last requirements implemented; still a 'restore' bug to fix
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 28 Dec 2017 13:55:11 +0000 (14:55 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 28 Dec 2017 13:55:11 +0000 (14:55 +0100)
README.md
css/index.css
index.html
js/index.js
scripts/rw_players.php

index fe69cc4..b9df387 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,13 +1,15 @@
 ## Pr&eacute;requis
 
 ## Pr&eacute;requis
 
-php (assez r&eacute;cent)
+php >= 5.4
 
 ## Ajustement du fichier de donn&eacute;es
 
  1. Renommer joueurs.csv.dist en joueurs.csv
 
 ## Ajustement du fichier de donn&eacute;es
 
  1. Renommer joueurs.csv.dist en joueurs.csv
- 2. &eacute;diter joueurs.csv (ajout de joueurs, &eacute;dition, suppression...). Format en lignes : pr&eacute;nom,nom[,score,pdt,pr&eacute;sent]
+ 2. &eacute;diter joueurs.csv (ajout de joueurs, &eacute;dition, suppression...). Format en lignes : pr&eacute;nom,nom[,pdt,session,pr&eacute;sent]
 
 
-pdt = "points de table". score,pdt,pr&eacute;sent : optionnels (d&eacute;faut 0, 0, 1)
+pdt, session, pr&eacute;sent : optionnels (par d&eacute;faut resp. 0, 0, 1).
+
+pdt = "points de table" ; session = "mini-points"
 
 ## Lancement de l'aplication
 
 
 ## Lancement de l'aplication
 
@@ -18,6 +20,8 @@ pdt = "points de table". score,pdt,pr&eacute;sent : optionnels (d&eacute;faut 0,
 
  1. Cliquer sur les joueurs absents dans l'onglet "joueurs"
  2. Aller dans la section "appariements" et cliquer sur le bouton en haut
 
  1. Cliquer sur les joueurs absents dans l'onglet "joueurs"
  2. Aller dans la section "appariements" et cliquer sur le bouton en haut
- 3. &Agrave; la fin d'une ronde, cliquer sur chaque table pour indiquer les points. Pour lancer la ronde suivante, revenir en 1)
+ 3. &Agrave; la fin d'une ronde, cliquer sur chaque table pour indiquer les (mini-)points. Pour lancer la ronde suivante, revenir en 1.
+
+Le classement est mis &agrave; jour dans la rubrique correspondante et dans joueurs.csv. Il peut être r&eacute;initialis&eacute; (bouton en haut).
 
 
-Le classement est mis &agrave; jour dans la rubrique correspondante et dans joueurs.csv
+Apr&egrave;s chaque op&eacute;ration sur les points, en cas d'erreur le bouton "Restaurer" en haut &agrave; droite annule la derni&egrave;re op&eacute;ration.
index d92ca1c..9604004 100644 (file)
@@ -65,6 +65,43 @@ table th {
        font-weight: bold;
 }
 
        font-weight: bold;
 }
 
+.left, .right {
+       float: left;
+       width: 50%;
+}
+
+.button-container-horizontal, .button-container-vertical {
+       margin-top: 30px;
+       text-align: center;
+}
+.button-container-vertical {
+       float: left;
+}
+/* All children except first: margin: 30px */
+.button-container-horizontal .btn:not(:first-child) {
+       margin-right: 30px;
+}
+.button-container-vertical .btn {
+       display: block;
+       margin-left: 30px;
+}
+.button-container-vertical .btn:not(:first-child) {
+       margin-top: 15px;
+}
+
+button.validate {
+       background-image: linear-gradient(to bottom, #6fa162, #40992e);
+}
+button.validate:hover {
+       background-image: linear-gradient(to bottom, #37cc4e, #40c24f);
+}
+button.cancel {
+       background-image: linear-gradient(to bottom, #d93470, #b82b47);
+}
+button.cancel:hover {
+       background-image: linear-gradient(to bottom, #fc433c, #d93434);
+}
+
 /* players div */
 
 #players {
 /* players div */
 
 #players {
@@ -77,11 +114,6 @@ table th {
        text-align: center;
 }
 
        text-align: center;
 }
 
-#active, #inactive {
-       float: left;
-       width: 50%;
-}
-
 #inactive table {
        opacity: 0.6;
 }
 #inactive table {
        opacity: 0.6;
 }
@@ -119,6 +151,7 @@ table.list tr:not(.title):hover, table.ranking tr:not(.title):nth-child(even):ho
 
 #ranking {
        margin-bottom: 15px;
 
 #ranking {
        margin-bottom: 15px;
+       overflow: auto;
 }
 
 table.ranking {
 }
 
 table.ranking {
@@ -126,6 +159,8 @@ table.ranking {
        width: 500px;
        margin: 0 auto;
        font-size: 1.1rem;
        width: 500px;
        margin: 0 auto;
        font-size: 1.1rem;
+       display: block;
+       float: left;
 }
 
 table.ranking td
 }
 
 table.ranking td
@@ -150,22 +185,19 @@ table.ranking tr:not(.title):hover, table.ranking tr:not(.title):nth-child(even)
 
 /* pairings div */
 
 
 /* pairings div */
 
-button.block {
-       display: block;
-       margin: 30px auto;
+.warning {
+       color: red;
+       margin-top: 25px;
 }
 
 }
 
-.button-container {
-       margin-top: 30px;
-       text-align: center;
+span.link {
+       color: darkblue;
+       cursor: pointer;
 }
 
 }
 
-button.cancel {
-       margin-left: 30px;
-       background-image: linear-gradient(to bottom, #d93470, #b82b47);
-}
-button.cancel:hover {
-       background-image: linear-gradient(to bottom, #fc433c, #d93434);
+button.block {
+       display: block;
+       margin: 25px auto;
 }
 
 .toto {
 }
 
 .toto {
@@ -194,9 +226,6 @@ td.score {
 .unpaired {
        cursor: default;
 }
 .unpaired {
        cursor: default;
 }
-.scored {
-       cursor: default;
-}
 
 .pairing > table {
        font-size: 1.1rem;
 
 .pairing > table {
        font-size: 1.1rem;
index 1360c13..cfbee36 100644 (file)
@@ -19,8 +19,8 @@
                        </header>
                        <main>
                                <my-players v-show="display=='players'" :players="players"></my-players>
                        </header>
                        <main>
                                <my-players v-show="display=='players'" :players="players"></my-players>
-                               <my-ranking v-show="display=='ranking'" :players="players" :sort-by-score="sortByScore" :rank-people="rankPeople"></my-ranking>
-                               <my-pairings v-show="display=='pairings'" :players="players" :sort-by-score="sortByScore"></my-pairings>
+                               <my-ranking v-show="display=='ranking'" :players="players" :sort-by-score="sortByScore" :write-score-to-db="writeScoreToDb"></my-ranking>
+                               <my-pairings v-show="display=='pairings'" :players="players" :write-score-to-db="writeScoreToDb"></my-pairings>
                        </main>
                </div>
        </body>
                        </main>
                </div>
        </body>
index 649e44c..a4a0dc7 100644 (file)
@@ -9,7 +9,7 @@ new Vue({
                        props: ['players'],
                        template: `
                                <div id="players">
                        props: ['players'],
                        template: `
                                <div id="players">
-                                       <div id="active">
+                                       <div class="left">
                                                <p>Présents</p>
                                                <table class="list">
                                                        <tr v-for="p in sortedPlayers" v-if="p.available" @click="toggleAvailability(p.index)">
                                                <p>Présents</p>
                                                <table class="list">
                                                        <tr v-for="p in sortedPlayers" v-if="p.available" @click="toggleAvailability(p.index)">
@@ -18,7 +18,7 @@ new Vue({
                                                        </tr>
                                                </table>
                                        </div>
                                                        </tr>
                                                </table>
                                        </div>
-                                       <div id="inactive">
+                                       <div id="inactive" class="right">
                                                <p>Absents</p>
                                                <table class="list">
                                                        <tr v-for="p in sortedPlayers" v-if="!p.available && p.nom!=''" @click="toggleAvailability(p.index)">
                                                <p>Absents</p>
                                                <table class="list">
                                                        <tr v-for="p in sortedPlayers" v-if="!p.available && p.nom!=''" @click="toggleAvailability(p.index)">
@@ -46,7 +46,7 @@ new Vue({
                        },
                },
                'my-ranking': {
                        },
                },
                'my-ranking': {
-                       props: ['players','sortByScore','rankPeople'],
+                       props: ['players','sortByScore','writeScoreToDb'],
                        template: `
                                <div id="ranking">
                                        <table class="ranking">
                        template: `
                                <div id="ranking">
                                        <table class="ranking">
@@ -63,6 +63,10 @@ new Vue({
                                                        <td>{{ p.session }}</td>
                                                </tr>
                                        </table>
                                                        <td>{{ p.session }}</td>
                                                </tr>
                                        </table>
+                                       <div class="button-container-vertical" style="width:200px">
+                                               <button class="btn cancel" @click="resetPlayers()">Réinitialiser</button>
+                                               <button id="restoreBtn" class="btn" @click="restoreLast()">Restaurer</button>
+                                       </div>
                                </div>
                        `,
                        computed: {
                                </div>
                        `,
                        computed: {
@@ -80,23 +84,65 @@ new Vue({
                                        return res;
                                },
                        },
                                        return res;
                                },
                        },
+                       methods: {
+                               rankPeople: function() {
+                                       return this.players
+                                               .slice(1) //discard Toto
+                                               .map( p => { return Object.assign({}, p); }) //to not alter original array
+                                               .sort(this.sortByScore);
+                               },
+                               resetPlayers: function() {
+                                       this.players
+                                               .slice(1) //discard Toto
+                                               .forEach( p => {
+                                                       p.pdt = 0;
+                                                       p.session = 0;
+                                                       p.available = 1;
+                                               });
+                                       this.writeScoreToDb();
+                                       document.getElementById("runPairing").click(); //TODO: hack...
+                               },
+                               restoreLast: function() {
+                                       let xhr = new XMLHttpRequest();
+                                       let self = this;
+                                       xhr.onreadystatechange = function() {
+                                               if (this.readyState == 4 && this.status == 200)
+                                               {
+                                                       let players = JSON.parse(xhr.responseText);
+                                                       if (players.length > 0)
+                                                       {
+                                                               players.unshift({ //add ghost 4th player for 3-players tables
+                                                                       prenom: "Toto",
+                                                                       nom: "",
+                                                                       pdt: 0,
+                                                                       session: 0,
+                                                                       available: 0,
+                                                               });
+                                                               self.players = players;
+                                                       }
+                                               }
+                                       };
+                                       xhr.open("GET", "scripts/rw_players.php?restore=1", true);
+                                       xhr.send(null);
+                               },
+                       },
                },
                'my-pairings': {
                },
                'my-pairings': {
-                       props: ['players','sortByScore'],
+                       props: ['players','writeScoreToDb'],
                        data: function() {
                                return {
                                        unpaired: [],
                                        tables: [], //array of arrays of players indices
                        data: function() {
                                return {
                                        unpaired: [],
                                        tables: [], //array of arrays of players indices
-                                       pdts: [], //"points de table" for each table
                                        sessions: [], //"mini-points" for each table
                                        currentIndex: -1, //table index for scoring
                                        sessions: [], //"mini-points" for each table
                                        currentIndex: -1, //table index for scoring
+                                       scored: [], //boolean for each table index
                                };
                        },
                        template: `
                                <div id="pairings">
                                        <div v-show="currentIndex < 0">
                                };
                        },
                        template: `
                                <div id="pairings">
                                        <div v-show="currentIndex < 0">
-                                               <button class="block btn" @click="shuffle()">Appariement</button>
-                                               <div class="pairing" v-for="(table,index) in tables" :class="{scored: pdts[index].length > 0}"
+                                               <button id="runPairing" class="block btn" @click="doPairings()">Nouvelle ronde</button>
+                                               <div class="pairing" v-for="(table,index) in tables" :class="{scored: scored[index]}"
                                                                @click="showScoreForm(table,index)">
                                                        <p>Table {{ index+1 }}</p>
                                                        <table>
                                                                @click="showScoreForm(table,index)">
                                                        <p>Table {{ index+1 }}</p>
                                                        <table>
@@ -122,9 +168,15 @@ new Vue({
                                                                <td><input type="text" v-model="sessions[currentIndex][i]" value="0"/></td>
                                                        </tr>
                                                </table>
                                                                <td><input type="text" v-model="sessions[currentIndex][i]" value="0"/></td>
                                                        </tr>
                                                </table>
-                                               <div class="button-container">
-                                                       <button class="btn" @click="setScore()">Enregistrer</button>
-                                                       <button class="btn cancel" @click="resetScore()">Annuler</button>
+                                               <div class="button-container-horizontal">
+                                                       <button class="btn validate" @click="setScore()">Enregistrer</button>
+                                                       <button class="btn" @click="currentIndex = -1">Fermer</button>
+                                               </div>
+                                               <div v-if="scored[currentIndex]" class="warning">
+                                                       Attention: un score a déjà été enregistré.
+                                                       Les points indiqués ici s'ajouteront : il faut d'abord
+                                                       <span class="link" @click="document.getElementById('restoreBtn').click()">restaurer l'état précédent.</span>
+                                                       Si c'est déjà fait, ignorer ce message :)
                                                </div>
                                        </div>
                                </div>
                                                </div>
                                        </div>
                                </div>
@@ -173,48 +225,43 @@ new Vue({
                                                        t.push(0); //index of "Toto", ghost player
                                        });
                                        this.tables = tables;
                                                        t.push(0); //index of "Toto", ghost player
                                        });
                                        this.tables = tables;
-                                       this.pdts = tables.map( t => { return []; }); //empty pdts
                                        this.sessions = tables.map( t => { return []; }); //empty sessions
                                        this.sessions = tables.map( t => { return []; }); //empty sessions
-                               },
-                               shuffle: function() {
-                                       this.doPairings();
+                                       this.scored = tables.map( t => { return false; }); //nothing scored yet
+                                       this.currentIndex = -1; //required if reset while scoring
                                },
                                showScoreForm: function(table,index) {
                                },
                                showScoreForm: function(table,index) {
-                                       if (this.pdts[index].length > 0)
-                                               return; //already scored
-                                       this.pdts[index] = _.times(table.length, _.constant(0));
-                                       this.sessions[index] = _.times(table.length, _.constant(0));
+                                       if (this.sessions[index].length == 0)
+                                               this.sessions[index] = _.times(table.length, _.constant(0));
                                        this.currentIndex = index;
                                },
                                setScore: function() {
                                        let sortedSessions = this.sessions[this.currentIndex]
                                                .map( (s,i) => { return {value:s, index:i}; })
                                                .sort( (a,b) => { return parseInt(b.value) - parseInt(a.value); });
                                        this.currentIndex = index;
                                },
                                setScore: function() {
                                        let sortedSessions = this.sessions[this.currentIndex]
                                                .map( (s,i) => { return {value:s, index:i}; })
                                                .sort( (a,b) => { return parseInt(b.value) - parseInt(a.value); });
-                                       let pdts = [4, 2, 1, 0]; //TODO: ex-aequos ?!
-                                       for (let i=0; i<this.tables[this.currentIndex].length; i++)
+                                       let pdts = [4, 2, 1, 0];
+                                       // NOTE: take care of ex-aequos (spread points subtotal)
+                                       let curSum = 0, curCount = 0, start = 0;
+                                       for (let i=0; i<4; i++)
                                        {
                                        {
-                                               this.players[this.tables[this.currentIndex][sortedSessions[i].index]].pdt += pdts[i];
+                                               // Update pdts:
+                                               curSum += pdts[i];
+                                               curCount++;
+                                               if (i==3 || sortedSessions[i].value > sortedSessions[i+1].value)
+                                               {
+                                                       let pdt = curSum / curCount;
+                                                       for (let j=start; j<=i; j++)
+                                                               this.players[this.tables[this.currentIndex][sortedSessions[j].index]].pdt += pdt;
+                                                       curSum = 0;
+                                                       curCount = 0;
+                                                       start = i+1;
+                                               }
+                                               // Update sessions:
                                                this.players[this.tables[this.currentIndex][i]].session += parseInt(this.sessions[this.currentIndex][i]);
                                        }
                                                this.players[this.tables[this.currentIndex][i]].session += parseInt(this.sessions[this.currentIndex][i]);
                                        }
+                                       this.scored[this.currentIndex] = true;
                                        this.currentIndex = -1;
                                        this.writeScoreToDb();
                                },
                                        this.currentIndex = -1;
                                        this.writeScoreToDb();
                                },
-                               resetScore: function() {
-                                       this.pdts[this.currentIndex] = [];
-                                       this.sessions[this.currentIndex] = [];
-                                       this.currentIndex = -1;
-                               },
-                               writeScoreToDb: function()
-                               {
-                                       let xhr = new XMLHttpRequest();
-                                       xhr.open("POST", "scripts/rw_players.php");
-                                       xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-                                       let orderedPlayers = this.players
-                                               .slice(1) //discard Toto
-                                               .map( p => { return Object.assign({}, p); }) //deep (enough) copy
-                                               .sort(this.sortByScore);
-                                       xhr.send("players="+encodeURIComponent(JSON.stringify(orderedPlayers)));
-                               },
                        },
                },
        },
                        },
                },
        },
@@ -226,7 +273,7 @@ new Vue({
                        {
                                let players = JSON.parse(xhr.responseText);
                                players.forEach( p => {
                        {
                                let players = JSON.parse(xhr.responseText);
                                players.forEach( p => {
-                                       p.pdt = !!p.pdt ? parseInt(p.pdt) : 0;
+                                       p.pdt = !!p.pdt ? parseFloat(p.pdt) : 0;
                                        p.session = !!p.session ? parseInt(p.session) : 0;
                                        p.available = !!p.available ? p.available : 1; //use integer for fputcsv PHP func
                                });
                                        p.session = !!p.session ? parseInt(p.session) : 0;
                                        p.available = !!p.available ? p.available : 1; //use integer for fputcsv PHP func
                                });
@@ -244,14 +291,19 @@ new Vue({
                xhr.send(null);
        },
        methods: {
                xhr.send(null);
        },
        methods: {
-               rankPeople: function() {
-                       return this.players
-                               .slice(1) //discard Toto
-                               .map( p => { return Object.assign({}, p); }) //to not alter original array
-                               .sort(this.sortByScore);
-               },
+               // Used both in ranking and pairings:
                sortByScore: function(a,b) {
                        return b.pdt - a.pdt + (Math.atan(b.session - a.session) / (Math.PI/2)) / 2;
                },
                sortByScore: function(a,b) {
                        return b.pdt - a.pdt + (Math.atan(b.session - a.session) / (Math.PI/2)) / 2;
                },
+               writeScoreToDb: function() {
+                       let xhr = new XMLHttpRequest();
+                       xhr.open("POST", "scripts/rw_players.php");
+                       xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+                       let orderedPlayers = this.players
+                               .slice(1) //discard Toto
+                               .map( p => { return Object.assign({}, p); }) //deep (enough) copy
+                               .sort(this.sortByScore);
+                       xhr.send("players="+encodeURIComponent(JSON.stringify(orderedPlayers)));
+               },
        },
 });
        },
 });
index b5bdabc..de63ad6 100644 (file)
@@ -2,6 +2,12 @@
 
 if (!isset($_POST["players"]))
 {
 
 if (!isset($_POST["players"]))
 {
+       if (isset($_GET["restore"]) && $_GET["restore"])
+       {
+               // Restore backup
+               if (!rename("../joueurs.csv.bak", "../joueurs.csv"))
+                       exit("[]");
+       }
        // Retrieve all players
        $handle = fopen("../joueurs.csv", "r");
        $players = [];
        // Retrieve all players
        $handle = fopen("../joueurs.csv", "r");
        $players = [];
@@ -23,6 +29,7 @@ if (!isset($_POST["players"]))
 }
 else
 {
 }
 else
 {
+       copy("../joueurs.csv", "../joueurs.csv.bak"); //backup current checkpoint
        // Write header + all players
        $handle = fopen("../joueurs.csv", "w");
        fputcsv($handle, ["prenom","nom","pdt","session","present"]);
        // Write header + all players
        $handle = fopen("../joueurs.csv", "w");
        fputcsv($handle, ["prenom","nom","pdt","session","present"]);