diff --git a/internal/models/vote.go b/internal/models/vote.go
index 1d504aa909009c4b5cbc090e6d6351b68550c35c..32a0fd6d345d747fd18d9e2fc4d18ee2c2e5cfb5 100644
--- a/internal/models/vote.go
+++ b/internal/models/vote.go
@@ -35,7 +35,7 @@ func (d *DataHandler) handleVote(w http.ResponseWriter, r *http.Request) {
 	case "PUT":
 		switch auth.GetLoggedUserTechnical(w, r).Role {
 		case "ADMIN", "CAPTURER":
-			d.putVote(w, r, id)
+			d.putVote(w, r)
 		case "VISUALIZER":
 			http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed)
 		default:
@@ -112,23 +112,32 @@ func (d *DataHandler) postVote(w http.ResponseWriter, r *http.Request) {
 
 }
 
-func (d *DataHandler) putVote(w http.ResponseWriter, r *http.Request, id int) {
-	var o Vote
-	if err := d.db.First(&o, id).Error; err != nil {
-		http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
-		return
-	}
+func (d *DataHandler) putVote(w http.ResponseWriter, r *http.Request) {
 	var vote Vote
 	err := json.NewDecoder(r.Body).Decode(&vote)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	// check that objects are the same
-	if o.CandidateListID != vote.CandidateListID || o.DeskRoundID != vote.DeskRoundID {
-		http.Error(w, "Les objets ne correspondent pas", http.StatusInternalServerError)
-		return
+
+	var o Vote
+	if vote.Blank {
+		if err := d.db.Where("blank = true  and desk_round_id = ?", vote.DeskRoundID).Find(&o).Error; err != nil {
+			http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
+			return
+		}
+	} else if vote.NullVote {
+		if err := d.db.Where("null_vote = true  and desk_round_id = ?", vote.DeskRoundID).Find(&o).Error; err != nil {
+			http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
+			return
+		}
+	} else {
+		if err := d.db.Where("candidate_list_id = ?  and desk_round_id = ?", vote.CandidateListID, vote.DeskRoundID).Find(&o).Error; err != nil {
+			http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
+			return
+		}
 	}
+
 	o.VoiceNumber = vote.VoiceNumber
 	d.db.Save(&o)
 	json.NewEncoder(w).Encode(o)
@@ -143,6 +152,16 @@ func (d *DataHandler) deleteVote(w http.ResponseWriter, r *http.Request, id int)
 			return
 		}
 		d.db.Delete(&o)
+
+		// Set completed to false for deskRound
+		var deskRound DeskRound
+		if err := d.db.First(&deskRound, o.DeskRoundID).Error; err != nil {
+			http.Error(w, ErrorParentNotFound, http.StatusNotFound)
+			return
+		}
+		deskRound.Completed = false
+		d.db.Save(&deskRound)
+
 	} else {
 		http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
 	}
diff --git a/internal/rootmux/admin_test.go b/internal/rootmux/admin_test.go
index 0da5abb840d71b9a14bf1f33566c99f90961f409..5e8fba7ceb6a1b715c879a8e96e2dad54d35a55b 100644
--- a/internal/rootmux/admin_test.go
+++ b/internal/rootmux/admin_test.go
@@ -124,7 +124,7 @@ func AdminTests(t *testing.T) {
 		// Get Votes
 		do("GET", "/api/Vote/", xsrfHeader, ``, 200, `[{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":158,"Blank":false,"NullVote":false}]`)
 		// Update a Vote
-		do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 200, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`)
+		do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`, 200, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`)
 
 		// TODO Update a DeskRound to Validated=true can only be done when votes are captured
 
diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go
index 6e0e9df93e2ccf02128e056f7e37cb95456e020f..2d3d47c985699f2b24197f306eb4f7b8275111ff 100644
--- a/internal/rootmux/rootmux_test.go
+++ b/internal/rootmux/rootmux_test.go
@@ -106,23 +106,27 @@ func appTests(t *testing.T) {
 		// 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 you can't update a Vote if it's not the same
-		do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":2,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 500, `Les objets ne correspondent pas`)
-		do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":2,"CandidateListID":2,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 500, `Les objets ne correspondent pas`)
-		do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":1,"CandidateListID":2,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 500, `Les objets ne correspondent pas`)
-
 		// Create Votes to complete a Desk
 		do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":3,"Blank":true}`, 200, `{"ID":2,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":3,"Blank":true,"NullVote":false}`)
 		do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":5,"NullVote":true}`, 200, `{"ID":3,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":5,"Blank":false,"NullVote":true}`)
 		do("GET", "/api/DeskRound/1", xsrfHeader, ``, 200, `{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":true,"DateCompletion":"20`)
 
+		// Check to update the good vote
+		do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`, 200, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`)
+		do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":158,"Blank":true,"NullVote":false}`, 200, `{"ID":2,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":158,"Blank":true,"NullVote":false}`)
+		do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":158,"Blank":false,"NullVote":true}`, 200, `{"ID":3,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":158,"Blank":false,"NullVote":true}`)
+
 		// Can't add the same vote several time
 		do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":158}`, 500, `Error the vote have already been captured`)
 		do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":3,"Blank":true}`, 500, `Error the vote have already been captured`)
 		do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":5,"NullVote":true}`, 500, `Error the vote have already been captured`)
 
+		//Check that on Vote deletion, deskRound is updated
+		do("DELETE", "/api/Vote/1", xsrfHeader, ``, 200, ``)
+		do("GET", "/api/DeskRound/1", xsrfHeader, ``, 200, `{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":false,"DateCompletion":"20`)
+
 		// 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":true,"DateCompletion":"20`)
+		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":"20`)
 		do("DELETE", "/api/Desk/1", xsrfHeader, ``, 200, ``)
 		do("GET", "/api/DeskRound/1", xsrfHeader, ``, 404, `id is missing`)
 
diff --git a/web/components/vote/desk-round.js b/web/components/vote/desk-round.js
index 34d08b50f32d314ee053c1e5513493b0bd1cba27..e37ac2ef81d19397f5082089d9d088d5280cb556 100644
--- a/web/components/vote/desk-round.js
+++ b/web/components/vote/desk-round.js
@@ -217,11 +217,10 @@ class DeskRoundSelector {
   }
 
   async openVotes(deskRound) {
-    this.parent.VoteHandler.displayVotes(
+    this.parent.voteHandler.displayVotes(
       this.RoundID,
       this.AreaID,
       deskRound.ID
     );
-    console.log("Ouverture des votes");
   }
 }
diff --git a/web/components/vote/votes.js b/web/components/vote/votes.js
index fb806024523ab5032bab28c5adecce76b99cf1d5..259ab779a059f3dcf6c446441d1cb2e4ab2db609 100644
--- a/web/components/vote/votes.js
+++ b/web/components/vote/votes.js
@@ -1,5 +1,13 @@
 // Imports
 import * as Auth from "/services/auth/auth.js";
+import * as ElectionModel from "/services/model/election-model.js";
+import * as RoundModel from "/services/model/round-model.js";
+import * as AreaModel from "/services/model/area-model.js";
+import * as SectionModel from "/services/model/section-model.js";
+import * as DeskModel from "/services/model/desk-model.js";
+import * as DeskRoundModel from "/services/model/deskRound-model.js";
+import * as VoteModel from "/services/model/vote-model.js";
+import * as CandidateListModel from "/services/model/candidateList-model.js";
 
 export async function mount(parent) {
   const voteComponent = new Vote(parent);
@@ -10,7 +18,258 @@ class Vote {
   constructor(parent) {
     this.method = null;
     this.parent = parent;
+    this.ElectionModel = ElectionModel.getElectionModel();
+    this.RoundModel = RoundModel.getRoundModel();
+    this.AreaModel = AreaModel.getAreaModel();
+    this.SectionModel = SectionModel.getSectionModel();
+    this.DeskModel = DeskModel.getDeskModel();
+    this.DeskRoundModel = DeskRoundModel.getDeskRoundModel();
+    this.VoteModel = VoteModel.getVoteModel();
+    this.CandidateListModel = CandidateListModel.getCandidateListModel();
   }
 
-  async displayVotes(RoundID, AreaID, DeskRoundID) {}
+  async displayVotes(RoundID, AreaID, DeskRoundID) {
+    this.ElectionModel.current_user = await Auth.GetUser();
+    this.RoundModel.current_user = await Auth.GetUser();
+    this.AreaModel.current_user = await Auth.GetUser();
+    this.SectionModel.current_user = await Auth.GetUser();
+    this.DeskModel.current_user = await Auth.GetUser();
+    this.DeskRoundModel.current_user = await Auth.GetUser();
+    this.VoteModel.current_user = await Auth.GetUser();
+    this.CandidateListModel.current_user = await Auth.GetUser();
+
+    this.RoundID = RoundID;
+    this.AreaID = AreaID;
+    this.DeskRoundID = DeskRoundID;
+
+    document.getElementById("vote-section").innerHTML = /* HTML */ `
+      <header class="card-header">
+      <p >
+        <nav class="breadcrumb card-header-title"  aria-label="breadcrumbs">
+          <ul id="vote-breadcrumb"></ul>
+        </nav>
+        </p>
+      </header>
+      <div id="votes-table" class="card-content"></div>
+      <nav class="level">
+        <div class="level-left"></div>
+        <div class="level-right">
+          <button id="votes-return" class="button level-item">
+            Retour
+          </button>
+          <button id="votes-cancel" class="button level-item">
+            Annuler
+          </button>
+          <button id="votes-delete" class="button is-danger level-item">
+            Supprimer
+          </button>
+          <button id="votes-save" class="button is-success level-item">
+            Sauvegarder
+          </button>
+        </div>
+      </nav>
+    `;
+    this.handleDom();
+
+    await this.refreshBreadCrumb();
+    await this.loadVotes();
+  }
+
+  voteTemplate(candidateList) {
+    return /* HTML */ `
+      <tr id="votes-vote-${candidateList.ID}">
+        <td>
+          ${candidateList.Name}
+        </td>
+        <td>
+          <input
+            class="input"
+            type="number"
+            id="${candidateList.ID}-vote-voice"
+          />
+        </td>
+      </tr>
+    `;
+  }
+
+  handleDom() {
+    let voteHandler = this;
+    document
+      .getElementById(`votes-return`)
+      .addEventListener("click", function () {
+        voteHandler.parent.deskRoundHandler.mount("vote-section");
+      });
+    document
+      .getElementById(`votes-cancel`)
+      .addEventListener("click", function () {
+        voteHandler.loadVotes();
+      });
+    document
+      .getElementById(`votes-save`)
+      .addEventListener("click", function () {
+        voteHandler.saveVotes();
+      });
+    document
+      .getElementById(`votes-delete`)
+      .addEventListener("click", function () {
+        voteHandler.deleteVotes();
+      });
+  }
+
+  async refreshBreadCrumb() {
+    let round = await this.RoundModel.getRound(this.RoundID);
+    let election = await this.ElectionModel.getElection(round.ElectionID);
+    let area = await this.AreaModel.getArea(this.AreaID);
+    let deskRound = await this.DeskRoundModel.getDeskRound(this.DeskRoundID);
+    let desk = await this.DeskModel.getDesk(deskRound.DeskID);
+    let section = await this.SectionModel.getSection(desk.SectionID);
+
+    let breadcrumb = document.getElementById("vote-breadcrumb");
+    let el = document.createElement("li");
+    el.innerHTML = "<a>" + election.Name + "</a>";
+    breadcrumb.appendChild(el);
+    el = document.createElement("li");
+    el.innerHTML =
+      "<a>tour : " +
+      round.Round +
+      ", date : " +
+      new Date(round.Date).toLocaleDateString() +
+      "</a>";
+    breadcrumb.appendChild(el);
+    el = document.createElement("li");
+    el.innerHTML = "<a>" + area.Name + "</a>";
+    breadcrumb.appendChild(el);
+    el = document.createElement("li");
+    el.innerHTML = "<a>" + section.Name + "</a>";
+    breadcrumb.appendChild(el);
+    el = document.createElement("li");
+    el.innerHTML = "<a>" + desk.Name + "</a>";
+    breadcrumb.appendChild(el);
+  }
+
+  async loadVotes() {
+    document.getElementById("votes-table").innerHTML = /* HTML */ `<div
+      class="table-container"
+    >
+      <table class="table is-bordered is-narrow is-hoverable is-fullwidth">
+        <thead>
+          <tr class="is-selected">
+            <th>Liste</th>
+            <th>Nombre de voix</th>
+          </tr>
+        </thead>
+        <tbody id="votes-list"></tbody>
+      </table>
+    </div> `;
+
+    let votes = await this.updatesVotes();
+    let candidateLists = await this.updateCandidateLists();
+
+    const markup = candidateLists
+      .map((vote) => this.voteTemplate(vote))
+      .join("");
+    document.getElementById("votes-list").innerHTML = markup;
+    document.getElementById("votes-list").innerHTML += /* HTML */ `
+    <tr">
+      <td>Votes blanc</td>
+      <td>
+        <input
+          class="input"
+          type="number"
+          id="blank-vote-voice"
+        />
+      </td>
+    </tr>
+  `;
+    document.getElementById("votes-list").innerHTML += /* HTML */ `
+      <tr>
+        <td>Votes nul</td>
+        <td>
+          <input class="input" type="number" id="null-vote-voice" />
+        </td>
+      </tr>
+    `;
+
+    votes.forEach((vote) => {
+      if (vote.Blank) {
+        document.getElementById("blank-vote-voice").value = vote.VoiceNumber;
+      } else if (vote.NullVote) {
+        document.getElementById("null-vote-voice").value = vote.VoiceNumber;
+      } else {
+        document.getElementById(vote.CandidateListID + "-vote-voice").value =
+          vote.VoiceNumber;
+      }
+    });
+  }
+
+  async saveVotes() {
+    let voteHandler = this;
+    let candidateLists = await this.updateCandidateLists();
+    let votes = await this.updatesVotes();
+
+    let method;
+    if (votes.length == 0) method = "POST";
+    else method = "PUT";
+
+    candidateLists.forEach(async (candidateList) => {
+      await voteHandler.VoteModel.saveVote(
+        method,
+        voteHandler.DeskRoundID,
+        candidateList.ID,
+        parseInt(
+          document.getElementById(candidateList.ID + "-vote-voice").value
+        ),
+        false,
+        false
+      );
+    });
+
+    await this.VoteModel.saveVote(
+      method,
+      this.DeskRoundID,
+      null,
+      parseInt(document.getElementById("blank-vote-voice").value),
+      true,
+      false
+    );
+    await this.VoteModel.saveVote(
+      method,
+      this.DeskRoundID,
+      null,
+      parseInt(document.getElementById("null-vote-voice").value),
+      false,
+      true
+    );
+    await this.VoteModel.refreshVotes();
+    await this.loadVotes();
+  }
+
+  async updatesVotes() {
+    let voteHandler = this;
+    let votes = await this.VoteModel.getVotes();
+    return votes.filter((vote) => {
+      return vote.DeskRoundID == voteHandler.DeskRoundID;
+    });
+  }
+
+  async updateCandidateLists() {
+    let voteHandler = this;
+    let candidateLists = await this.CandidateListModel.getCandidateLists();
+    return candidateLists.filter((candidateList) => {
+      return (
+        candidateList.AreaID == voteHandler.AreaID &&
+        candidateList.RoundID == voteHandler.RoundID
+      );
+    });
+  }
+
+  async deleteVotes() {
+    let voteHandler = this;
+    let votes = await this.updatesVotes();
+    votes.forEach(async (vote) => {
+      await voteHandler.VoteModel.deleteVote(vote.ID);
+      await voteHandler.VoteModel.refreshVotes();
+      await voteHandler.loadVotes();
+    });
+  }
 }
diff --git a/web/services/model/vote-model.js b/web/services/model/vote-model.js
new file mode 100644
index 0000000000000000000000000000000000000000..ac75693e5f23281c54eda48557a2edb34b6ab57e
--- /dev/null
+++ b/web/services/model/vote-model.js
@@ -0,0 +1,105 @@
+import * as Messages from "/services/messages/messages.js";
+
+let voteModel;
+
+export function getVoteModel() {
+  if (voteModel == null) {
+    voteModel = new VoteModel();
+  }
+  return voteModel;
+}
+
+class VoteModel {
+  constructor() {}
+
+  async getVote(id) {
+    if (this.votes == null) await this.refreshVotes();
+    let voteToGet;
+    this.votes.forEach((vote) => {
+      if (vote.ID == id) voteToGet = vote;
+    });
+    return voteToGet;
+  }
+
+  async getVotes() {
+    if (this.votes == null) {
+      try {
+        const response = await fetch("/api/Vote/", {
+          method: "GET",
+          headers: new Headers({
+            "XSRF-Token": this.current_user.xsrftoken,
+          }),
+        });
+        if (response.status !== 200) {
+          throw new Error(
+            `Votes could not be fetched (status ${response.status})`
+          );
+        }
+        this.votes = await response.json();
+      } catch (e) {
+        Messages.Show("is-warning", e.message);
+        console.error(e);
+      }
+    }
+    return this.votes;
+  }
+
+  async saveVote(
+    method,
+    DeskRoundID,
+    CandidateListID,
+    VoiceNumber,
+    Blank,
+    NullVote
+  ) {
+    try {
+      const response = await fetch("/api/Vote/", {
+        method: method,
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+        body: JSON.stringify({
+          DeskRoundID: DeskRoundID,
+          CandidateListID: CandidateListID,
+          VoiceNumber: VoiceNumber,
+          Blank: Blank,
+          NullVote: NullVote,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Vote could not be updated or created (status ${response.status})`
+        );
+      }
+      return await response.json();
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+      return;
+    }
+  }
+
+  async deleteVote(ID) {
+    try {
+      const response = await fetch("/api/Vote/" + ID, {
+        method: "delete",
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Vote could not be deleted (status ${response.status})`
+        );
+      }
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+    }
+  }
+
+  async refreshVotes() {
+    this.votes = null;
+    await this.getVotes();
+  }
+}
diff --git a/web/style.css b/web/style.css
index df4b86c774e0316302ab7cf4d7b26d06d5b017f9..6d759601971ee417ae2223c627037b312ed3a717 100644
--- a/web/style.css
+++ b/web/style.css
@@ -135,8 +135,13 @@ select {
 
 .upper-text {
   writing-mode: sideways-lr;
-  background-color: rgba(55,122,195,.95);
+  background-color: rgba(55, 122, 195, 0.95);
   text-orientation: sideways-right;
   text-align: center;
   cursor: pointer;
 }
+
+#vote-breadcrumb a {
+  color: #000;
+  cursor: default;
+}