diff --git a/api/index.js b/api/index.js index 868c1be5ba33537ffb040ad4538855131b7dd309..6cc1ba73869ba468818ab2bc5da488638b7d88da 100644 --- a/api/index.js +++ b/api/index.js @@ -8,12 +8,14 @@ const express_1 = __importDefault(require("express")); const dotenv_1 = __importDefault(require("dotenv")); const mysql2_1 = __importDefault(require("mysql2")); const cors_1 = __importDefault(require("cors")); +const http_status_codes_1 = __importDefault(require("http-status-codes")); +let bodyParser = require('body-parser'); dotenv_1.default.config({ path: __dirname + '/../.env' }); exports.db = mysql2_1.default.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PWD, - database: process.env.DB_NAME, + database: process.env.DB_NAME }); const options = { origin: "*" @@ -21,6 +23,11 @@ const options = { const app = (0, express_1.default)(); const port = process.env.API_PORT; app.use((0, cors_1.default)(options)); +app.use(express_1.default.json()); +app.use(express_1.default.urlencoded()); +/* +* ------------------------------- EDITIONS -------------------------------------- +*/ app.get('/editions', (req, res) => { const query = 'SELECT * FROM edition'; exports.db.query(query, [], (err, result) => { @@ -30,7 +37,44 @@ app.get('/editions', (req, res) => { res.json(result); }); }); +/* +* ------------------------------- TAGS -------------------------------------- +*/ +// Get all tags +app.get('/tags', (req, res) => { + const query = 'SELECT * FROM tag'; + exports.db.query(query, [], (err, result) => { + if (err) { + res.send(err); + } + res.json(result); + }); +}); +// Get all tags available for the edition +app.get('/tags/edition', (req, res) => { + let year = req.query.year; + const query = 'SELECT * FROM tag WHERE tag.id_tag NOT IN ( SELECT tag.id_tag FROM tag_team_edition LEFT JOIN tag ON tag.id_tag = tag_team_edition.id_tag WHERE tag_team_edition.year = ? ) '; + exports.db.query(query, [year], (err, result) => { + if (err) { + res.send(err); + } + res.json(result); + }); +}); +/* +* ------------------------------- TEAMS -------------------------------------- +*/ app.get('/teams', (req, res) => { + const query = 'SELECT * FROM team'; + const year = req.query.year; + exports.db.query(query, [year], (err, result) => { + if (err) { + res.send(err); + } + res.json(result); + }); +}); +app.get('/teams/tag', (req, res) => { const query = 'SELECT team.id_team, team.name, tag_team_edition.id_tag ' + 'FROM `tag_team_edition` ' + 'LEFT JOIN team ON tag_team_edition.id_team = team.id_team ' + @@ -43,6 +87,31 @@ app.get('/teams', (req, res) => { res.json(result); }); }); +app.post('/teams', (req, res) => { + let team_name = req.body.team_name; + let team_participants = req.body.team_participants; + let year = req.body.year_selected; + let tag = req.body.tag_selected; + if (!team_name || !team_participants || !year || !tag) + res.status(http_status_codes_1.default.BAD_REQUEST).send({ error: "Missing arguments" }); + const query = 'INSERT INTO team(name, participants) VALUES (?,?)'; + exports.db.query(query, [team_name, team_participants], (err, result) => { + if (err) { + res.status(http_status_codes_1.default.CONFLICT).send({ error: "Team name already exists!" }); + } + let id_team = result.insertId; + const query_tte = "INSERT INTO `tag_team_edition`(`id_tag`, `id_team`, `year`) VALUES (?,?,?)"; + exports.db.query(query_tte, [tag, id_team, year], (err, result) => { + if (err) { + res.status(http_status_codes_1.default.INTERNAL_SERVER_ERROR).send({ error: "Something wrong append" }); + } + res.status(http_status_codes_1.default.OK).send(); + }); + }); +}); +/* +* ------------------------------- Entry / results -------------------------------------- +*/ app.get('/entry', (req, res) => { const query = 'SELECT entry.time_stamp, tag_team_edition.year, entry.id_tag, tag_team_edition.year, team.name ' + 'FROM `entry` ' + diff --git a/api/index.ts b/api/index.ts index e3c75300ceb16c8d2675913f3e2c076af462f6b8..fb70d5532aa107c7cf4daa3a1725bb33103f7902 100644 --- a/api/index.ts +++ b/api/index.ts @@ -2,25 +2,33 @@ import express, { Express, Request, Response } from 'express'; import dotenv from 'dotenv'; import mysql from "mysql2"; import cors from 'cors'; +import StatusCode from 'http-status-codes' -dotenv.config({path:__dirname+'/../.env'}); +let bodyParser = require('body-parser') + +dotenv.config({ path: __dirname + '/../.env' }); export const db = mysql.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PWD, - database: process.env.DB_NAME, + database: process.env.DB_NAME }) const options: cors.CorsOptions = { - origin: "*" + origin: "*" }; const app = express(); const port = process.env.API_PORT; app.use(cors(options)); +app.use(express.json()); +app.use(express.urlencoded()); +/* +* ------------------------------- EDITIONS -------------------------------------- +*/ app.get('/editions', (req: Request, res: Response) => { const query = 'SELECT * FROM edition'; @@ -30,14 +38,46 @@ app.get('/editions', (req: Request, res: Response) => { } res.json(result); - } + }) +}); + +/* +* ------------------------------- TAGS -------------------------------------- +*/ +// Get all tags +app.get('/tags', (req: Request, res: Response) => { + const query = 'SELECT * FROM tag'; + + db.query(query, [], (err, result) => { + if (err) { + res.send(err); + } + + res.json(result); + }) }); +// Get all tags available for the edition +app.get('/tags/edition', (req: Request, res: Response) => { + let year = req.query.year; + + const query = 'SELECT * FROM tag WHERE tag.id_tag NOT IN ( SELECT tag.id_tag FROM tag_team_edition LEFT JOIN tag ON tag.id_tag = tag_team_edition.id_tag WHERE tag_team_edition.year = ? ) '; + + db.query(query, [year], (err, result) => { + if (err) { + res.send(err); + } + + res.json(result); + }) +}); + + +/* +* ------------------------------- TEAMS -------------------------------------- +*/ app.get('/teams', (req: Request, res: Response) => { - const query = 'SELECT team.id_team, team.name, tag_team_edition.id_tag ' + - 'FROM `tag_team_edition` ' + - 'LEFT JOIN team ON tag_team_edition.id_team = team.id_team ' + - 'WHERE year = ?'; + const query = 'SELECT * FROM team'; const year = req.query.year @@ -48,28 +88,77 @@ app.get('/teams', (req: Request, res: Response) => { res.json(result); } -) - + ) }); -app.get('/entry', (req: Request, res: Response) => { - const query = 'SELECT entry.time_stamp, tag_team_edition.year, entry.id_tag, tag_team_edition.year, team.name '+ - 'FROM `entry` ' + - 'LEFT JOIN tag ON entry.id_tag = tag.id_tag ' + - 'LEFT JOIN tag_team_edition ON tag.id_tag = tag_team_edition.id_tag ' + - 'LEFT JOIN team ON tag_team_edition.id_team = team.id_team ' + - 'WHERE tag_team_edition.year = ? ' + - 'ORDER BY entry.time_stamp ASC'; +app.get('/teams/tag', (req: Request, res: Response) => { + const query = 'SELECT team.id_team, team.name, tag_team_edition.id_tag ' + + 'FROM `tag_team_edition` ' + + 'LEFT JOIN team ON tag_team_edition.id_team = team.id_team ' + + 'WHERE year = ?'; const year = req.query.year db.query(query, [year], (err, result) => { + if (err) { + res.send(err); + } + + res.json(result); + } + ) +}); + +app.post('/teams', (req: Request, res:Response) => { + let team_name = req.body.team_name; + let team_participants = req.body.team_participants; + let year = req.body.year_selected; + let tag = req.body.tag_selected; + + if(!team_name || !team_participants || !year || !tag) + res.status(StatusCode.BAD_REQUEST).send({error: "Missing arguments"}); + + const query = 'INSERT INTO team(name, participants) VALUES (?,?)'; + + db.query(query, [team_name, team_participants], (err, result) => { + if (err) { + res.status(StatusCode.CONFLICT).send({error: "Team name already exists!"}); + } + + let id_team = result.insertId; + const query_tte = "INSERT INTO `tag_team_edition`(`id_tag`, `id_team`, `year`) VALUES (?,?,?)"; + + db.query(query_tte, [tag, id_team, year], (err, result) => { if (err) { - res.send("ERROR"); + res.status(StatusCode.INTERNAL_SERVER_ERROR).send({error: "Something wrong append"}); } - res.json(result); + res.status(StatusCode.OK).send(); + }); + }); +}) + +/* +* ------------------------------- Entry / results -------------------------------------- +*/ +app.get('/entry', (req: Request, res: Response) => { + const query = 'SELECT entry.time_stamp, tag_team_edition.year, entry.id_tag, tag_team_edition.year, team.name ' + + 'FROM `entry` ' + + 'LEFT JOIN tag ON entry.id_tag = tag.id_tag ' + + 'LEFT JOIN tag_team_edition ON tag.id_tag = tag_team_edition.id_tag ' + + 'LEFT JOIN team ON tag_team_edition.id_team = team.id_team ' + + 'WHERE tag_team_edition.year = ? ' + + 'ORDER BY entry.time_stamp ASC'; + + const year = req.query.year + + db.query(query, [year], (err, result) => { + if (err) { + res.send("ERROR"); } + + res.json(result); + } ) }) diff --git a/api/package-lock.json b/api/package-lock.json index cb5a60046c9e68a4ca71473a2f59f817d7636a50..d9d379f6049b4b2d21b36be8608ece1d337697e9 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -13,6 +13,7 @@ "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", + "http-status-codes": "^2.2.0", "mysql": "^2.18.1", "mysql2": "^2.3.3" }, @@ -734,6 +735,11 @@ "node": ">= 0.8" } }, + "node_modules/http-status-codes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", + "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2178,6 +2184,11 @@ "toidentifier": "1.0.1" } }, + "http-status-codes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", + "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/api/package.json b/api/package.json index 84f8cd3bde6853f2f5319ec1514ee4140fc605de..2b336a84517533fb3166d41c104078068b22076c 100644 --- a/api/package.json +++ b/api/package.json @@ -16,6 +16,7 @@ "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", + "http-status-codes": "^2.2.0", "mysql": "^2.18.1", "mysql2": "^2.3.3" }, diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index dc15c315e5a3d0a69b5a664a8777a42909861dfe..a6a00817e76105f11db04b495154f07ad211cfde 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -3,13 +3,15 @@ import { Router, Link, Route } from "svelte-navigator"; import Teams from "./lib/Teams.svelte"; import Entry from "./lib/Entry.svelte"; + import Team from "./lib/Team.svelte"; </script> <Router> <header> - <h1>Boldor</h1> + <h1 id="title">Boldor</h1> <Link to="/">Home</Link> <Link to="teams">Équipes</Link> + <Link to="team">Gestion des équipes</Link> <Link to="entry">Résultats</Link> </header> @@ -38,6 +40,10 @@ <Teams /> </Route> + <Route path="team"> + <Team /> + </Route> + <Route path="entry"> <Entry /> </Route> @@ -49,4 +55,10 @@ height: 100px; text-align: center; } + + main { + width: 80%; + max-width: 1024px; + margin: auto; + } </style> diff --git a/frontend/src/app.css b/frontend/src/app.css index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..69dd220cc487f862b1911b8d65b0be8c465850e7 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -0,0 +1,82 @@ +body, html { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +table, +tr, +td{ + border: 1px solid black; +} + +tr:nth-child(even){background-color: #f2f2f2;} +tr:hover {background-color: #ddd;} + +table { + border-collapse: collapse; + width: 100%; + margin-top: 15px; + margin-bottom: 15px; +} + +th { + background-color: #061b41; + color: white; + font-size: 1.2em; + padding: 5px; +} + +select, button { + background-color: #1251c7; + color: white; + border: 0; + padding: 10px; + font-size: 1.1em; + font-weight: bold; + border-radius: 5px; +} + +button:hover { + background-color: #0a2f74; +} + +.container { + width: 80%; + margin: auto; + max-width: 1024px; + padding: 15px; +} + +form { + width: 100%; +} + +.form-group { + width: 100%; + margin-top: 20px; +} + +.form-group label, +input { + display: block; + font-size: 1.2em; + width: 99%; + border-radius: 5px; +} + +.form-group input { + padding: 5px; + border: 1px solid black; +} + +.form-group textarea { + width: 99%; + resize: none; + border-radius: 5px; + border: 1px solid black; +} + +form button { + margin-top: 20px; + font-size: 1.3em; + padding: 5px; +} \ No newline at end of file diff --git a/frontend/src/lib/Entry.svelte b/frontend/src/lib/Entry.svelte index c1dec9a73b657e7ecc15088727feedbae25849f4..6e1711292fdb63a7e57c16cf6d82b864482ccfeb 100644 --- a/frontend/src/lib/Entry.svelte +++ b/frontend/src/lib/Entry.svelte @@ -1,77 +1,62 @@ <script> const edition_url = "http://localhost:8000/editions"; - + let editions = []; fetch(edition_url) - .then((response) => response.json()) - .then((editions_data) => { - editions = editions_data; - }) - + .then((response) => response.json()) + .then((editions_data) => { + editions = editions_data; + }); + let entries = []; function getEntries(year) { - let entries_url = "http://localhost:8000/entry?year="+year; + let entries_url = "http://localhost:8000/entry?year=" + year; fetch(entries_url) - .then((response) => response.json()) - .then((entries_data) => { - entries = entries_data; - }) + .then((response) => response.json()) + .then((entries_data) => { + entries = entries_data; + }); } - + let year_selected = new Date().getFullYear(); - function selectYear(){ + function selectYear() { getEntries(year_selected); } - + function parseTimeStamp(time_stamp) { - return (time_stamp.split("T")[1]).split(".")[0] + return time_stamp.split("T")[1].split(".")[0]; } - getEntries(year_selected) - </script> - - <div class="container"> - <h1>Ordre de passage {year_selected}</h1> - - <select name="" id="" bind:value={year_selected} on:change={selectYear}> - {#each editions as edition } - <option value="{edition.year}">{edition.year}</option> - {/each} - </select> - - <table> - <tr> - <th>Heure de passage</th> - <th>Tag RFID</th> - <th>Équipe</th> - <th>Actions</th> - </tr> - {#each entries as entry} + getEntries(year_selected); +</script> + +<div class="container"> + <h1>Classement {year_selected}</h1> + + <select name="" id="" bind:value={year_selected} on:change={selectYear}> + {#each editions as edition} + <option value={edition.year}>{edition.year}</option> + {/each} + </select> + + <table> + <tr> + <th>Heure de passage</th> + <th>Tag RFID</th> + <th>Équipe</th> + <th>Actions</th> + </tr> + {#each entries as entry} <tr> <td>{parseTimeStamp(entry.time_stamp)}</td> <td>{entry.id_tag}</td> <td>{entry.name}</td> <td>...</td> </tr> - {/each} - </table> - </div> - - <style> - .container { - width: 80%; - margin: auto; - max-width: 1024px; - background-color: rgb(235, 235, 235); - padding: 15px; - } - - table, tr, td, th{ - border: 1px solid black; - } - - table { - border-collapse: collapse; - width: 100%; - } - </style> \ No newline at end of file + {/each} + </table> +</div> + +<style> + +</style> diff --git a/frontend/src/lib/Team.svelte b/frontend/src/lib/Team.svelte new file mode 100644 index 0000000000000000000000000000000000000000..69eae7f31780c361177a8412d1b92a0ad1415ab5 --- /dev/null +++ b/frontend/src/lib/Team.svelte @@ -0,0 +1,142 @@ +<script> + import Teams from "./Teams.svelte"; + + let teams = []; + let availableTags = []; + + let TEAMS_URL = "http://localhost:8000/teams"; + function getTeams() { + fetch(TEAMS_URL) + .then((response) => response.json()) + .then((teams_data) => { + teams = teams_data; + }); + } + + let TAGS_URL = "http://localhost:8000/tags/edition?year="; + function getAvailableTags(year) { + fetch(TAGS_URL+year) + .then((response) => response.json()) + .then((tags) => { + availableTags = tags; + }); + } + + const edition_url = "http://localhost:8000/editions"; + let editions = []; + fetch(edition_url) + .then((response) => response.json()) + .then((editions_data) => { + editions = editions_data; + }); + + let team_name = ""; + let team_participants = ""; + let tag_selected = ""; + let year_selected = new Date().getFullYear(); + + // Send data + function save() { + if(!team_name || !team_participants || !tag_selected || !year_selected) { + alert("Vous devez remplir tous les champs"); + return + } + + let body_data = { + team_name: team_name, + team_participants: team_participants, + tag_selected: tag_selected, + year_selected: year_selected + }; + + fetch(TEAMS_URL, { + method: "POST", + body: JSON.stringify(body_data), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }) + .then(function (response) { + if (!response.ok) return Promise.reject(response.status); + getTeams(); + return response.json(); + }) + .then(function (data) { + console.log("okey" + data); + }) + .catch(function (error) { + console.log("Error: " + error); + }); + + } + + function selectYear(){ + getAvailableTags(year_selected); + } + + getTeams() + getAvailableTags(year_selected); +</script> + +<div class="container"> + <div id="create"> + <form action="#"> + <div class="form-group"> + <select name="" id="" bind:value={year_selected} on:click={selectYear}> + {#each editions as edition} + <option value={edition.year}>{edition.year}</option> + {/each} + </select> + </div> + <div class="form-group"> + <label for="name">Nom d'équipe</label> + <input type="text" id="name" bind:value={team_name} /> + </div> + <div class="form-group"> + <label for="participant">Participants</label> + <textarea + name="participant" + id="participant" + rows="10" + bind:value={team_participants} + /> + </div> + <div class="form-group"> + <select name="" id="" bind:value={tag_selected}> + {#each availableTags as tag, i} + {#if i == 1} + <option value={tag.id_tag} selected>{tag.id_tag}</option> + {:else} + <option value={tag.id_tag}>{tag.id_tag}</option> + {/if} + {/each} + </select> + + </div> + <button on:click|preventDefault={save}>Enregistrer</button> + </form> + </div> + + <div id="team"> + <table> + <tr> + <th>Id team</th> + <th>Team name</th> + <th>Participants</th> + <th>Action</th> + </tr> + {#each teams as team} + <tr> + <td>{team.id_team}</td> + <td>{team.name}</td> + <td>{team.participants}</td> + <td>...</td> + </tr> + {/each} + </table> + </div> +</div> + +<style> + +</style> diff --git a/frontend/src/lib/Teams.svelte b/frontend/src/lib/Teams.svelte index dbf5a351af83d28f1aeee27d0068e6fabaa3fc5c..20688e9112195e76d262006e88e361c514cf0895 100644 --- a/frontend/src/lib/Teams.svelte +++ b/frontend/src/lib/Teams.svelte @@ -1,37 +1,37 @@ <script> -const edition_url = "http://localhost:8000/editions"; - -let editions = []; -fetch(edition_url) -.then((response) => response.json()) -.then((editions_data) => { - editions = editions_data; -}) + const edition_url = "http://localhost:8000/editions"; + + let editions = []; + fetch(edition_url) + .then((response) => response.json()) + .then((editions_data) => { + editions = editions_data; + }); + + let teams = []; + function getTeams(year) { + let teams_url = "http://localhost:8000/teams/tag?year=" + year; + fetch(teams_url) + .then((response) => response.json()) + .then((teams_data) => { + teams = teams_data; + }); + } -let teams = []; -function getTeams(year) { - let teams_url = "http://localhost:8000/teams?year="+year; - fetch(teams_url) - .then((response) => response.json()) - .then((teams_data) => { - teams = teams_data; - }) -} + let year_selected = new Date().getFullYear(); + function selectYear() { + getTeams(year_selected); + } -let year_selected = new Date().getFullYear(); -function selectYear(){ getTeams(year_selected); -} - -getTeams(year_selected) </script> <div class="container"> <h1>Liste des équipe pour l'années {year_selected}</h1> <select name="" id="" bind:value={year_selected} on:change={selectYear}> - {#each editions as edition } - <option value="{edition.year}">{edition.year}</option> + {#each editions as edition} + <option value={edition.year}>{edition.year}</option> {/each} </select> @@ -42,30 +42,15 @@ getTeams(year_selected) <th>Action</th> </tr> {#each teams as team} - <tr> - <td>{team.id_tag}</td> - <td>{team.name}</td> - <td>...</td> - </tr> + <tr> + <td>{team.id_tag}</td> + <td>{team.name}</td> + <td>...</td> + </tr> {/each} </table> </div> <style> - .container { - width: 80%; - margin: auto; - max-width: 1024px; - background-color: rgb(235, 235, 235); - padding: 15px; - } - table, tr, td, th{ - border: 1px solid black; - } - - table { - border-collapse: collapse; - width: 100%; - } -</style> \ No newline at end of file +</style>