diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 60569b27ae4730ce75455d25ea7d49cf6f57da3c..a3c50005801fa85cf100c865338cb5dd54a5c203 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,29 +1,29 @@ <script lang="ts"> import { onMount } from "svelte"; - - import NavBar from "./components/NavBar.svelte"; - import Registrations from "./components/Registrations.svelte"; + import NavBar from "./common/NavBar.svelte"; + import Registrations from "./components/Seedings/Registrations.svelte"; import * as Session from "../bindings/changeme/astro/services/session"; import { Competition } from "../bindings/changeme/astro/services/models"; - import NewPlayerForm from "./components/NewPlayerForm.svelte"; let competitions: Competition[] = []; - - let competitionID = 0; + let loading = true; onMount(async () => { competitions = await Session.GetCompetitions(); + loading = false; }); </script> <div class="container"> <NavBar /> - <NewPlayerForm /> - <!-- {#if competitions.length <= competitionID} + <!-- Get competition ID --> + {#if loading} <p>Loading...</p> + {:else if competitions.length > 0} + <Registrations /> {:else} - <Registrations competition={competitions[competitionID]} /> - {/if} --> + <p>No competitions available or invalid competition ID.</p> + {/if} </div> <style> diff --git a/frontend/src/components/NavBar.svelte b/frontend/src/common/NavBar.svelte similarity index 85% rename from frontend/src/components/NavBar.svelte rename to frontend/src/common/NavBar.svelte index 101e278c63f131015b0fb8a5c4c8448b8f39393b..17f3261ad6b33b4d85d49eb4cf85f89d85e70289 100644 --- a/frontend/src/components/NavBar.svelte +++ b/frontend/src/common/NavBar.svelte @@ -7,6 +7,7 @@ RemoveCompetition, } from "../../bindings/changeme/astro/services/session"; import { onMount } from "svelte"; + import { SelectedCompetition } from "../store"; let competitions: Competition[] = []; @@ -19,16 +20,23 @@ <div class="nav-bar"> <div class="competition-container"> + <!-- svelte-ignore a11y-no-static-element-interactions --> {#each competitions as competition} + <!-- svelte-ignore a11y-click-events-have-key-events --> <div class="competition-el" style="background-color: {randomColor(rightBrighness)};" + on:click={() => { + SelectedCompetition.set(competition); + }} > <span>{competition.CompetitionName}</span> <button on:click={async () => { await RemoveCompetition(competition.CompetitionID); loadCompetitions(); + + SelectedCompetition.set(undefined); }}>X</button > </div> diff --git a/frontend/src/components/Registrations.svelte b/frontend/src/components/Registrations.svelte deleted file mode 100644 index 89632bda7d266e59f02aedf903c65323c437d0d5..0000000000000000000000000000000000000000 --- a/frontend/src/components/Registrations.svelte +++ /dev/null @@ -1,248 +0,0 @@ -<script lang="ts"> - import { onMount } from "svelte"; - import * as Models from "../../bindings/changeme/astro/services/models"; - import * as Session from "../../bindings/changeme/astro/services/session"; - import swal from "sweetalert"; - - export let competition: Models.Competition; - - let players: (Models.Player | null)[] = []; - let filteredPlayers: (Models.Player | null)[] = []; - - async function loadPlayers() { - Session.GetAllPlayersFromCompetition(competition.CompetitionID).then( - (result) => { - players = result; - } - ); - } - - onMount(async () => { - await loadPlayers(); - }); - - function getPlayerValueByKey(player: Models.Player | null, key: string) { - if (player === null) { - return ""; - } - - switch (key) { - case "PlayerInitialRank": - return player.PlayerInitialRank; - case "PlayerFirstname": - return player.PlayerFirstname; - case "PlayerLastname": - return player.PlayerLastname; - case "PlayerClub": - return player.PlayerClub; - default: - return ""; - } - } - - let sortBy = { key: "PlayerInitialRank", asc: true }; - - $: players = players.sort((a, b) => { - let aValue = getPlayerValueByKey(a, sortBy.key); - let bValue = getPlayerValueByKey(b, sortBy.key); - if (aValue === null || bValue === null) { - return 0; - } - if (aValue < bValue) { - return sortBy.asc ? -1 : 1; - } - if (aValue > bValue) { - return sortBy.asc ? 1 : -1; - } - return 0; - }); - - let searchTerms = ""; - - $: filteredPlayers = players.filter((player) => { - if (player === null) { - return []; - } - - return ( - player.PlayerFirstname.toLowerCase().includes( - searchTerms.toLowerCase() - ) || - player.PlayerLastname.toLowerCase().includes( - searchTerms.toLowerCase() - ) || - player.PlayerClub?.club_name - .toLowerCase() - .includes(searchTerms.toLowerCase()) - ); - }); -</script> - -<div class="registration-container"> - <input - type="text" - bind:value={searchTerms} - placeholder="Search for a player" - /> - <table> - <thead> - <tr> - <th - on:click={() => { - sortBy = { key: "PlayerInitialRank", asc: !sortBy.asc }; - }}>Initial Rank</th - > - <th - on:click={() => { - sortBy = { key: "PlayerFirstname", asc: !sortBy.asc }; - }}>Firstname</th - > - <th - on:click={() => { - sortBy = { key: "PlayerLastname", asc: !sortBy.asc }; - }}>Lastname</th - > - <th - on:click={() => { - sortBy = { key: "PlayerClub", asc: !sortBy.asc }; - }}>Club</th - > - </tr> - </thead> - <tbody> - {#each filteredPlayers as player} - {#if player != null} - <tr> - <td - on:dblclick={async () => { - let newRank = await swal({ - content: { - element: "input", - attributes: { - placeholder: "New rank", - type: "number", - }, - }, - buttons: { - cancel: true, - confirm: true, - }, - }); - - if (newRank) { - player.PlayerInitialRank = - parseInt(newRank); - await Session.UpdateCompetitionPlayer( - competition.CompetitionID, - player - ).then(() => { - loadPlayers(); - }); - } - }}>{player.PlayerInitialRank}</td - > - <td - on:dblclick={async () => { - let newFirstname = await swal({ - content: { - element: "input", - attributes: { - placeholder: "New firstname", - }, - }, - buttons: { - cancel: true, - confirm: true, - }, - }); - - if (newFirstname) { - player.PlayerFirstname = newFirstname; - await Session.UpdateCompetitionPlayer( - competition.CompetitionID, - player - ).then(() => { - loadPlayers(); - }); - } - }}>{player.PlayerFirstname}</td - > - <td - on:dblclick={async () => { - let newLastname = await swal({ - content: { - element: "input", - attributes: { - placeholder: "New lastname", - }, - }, - buttons: { - cancel: true, - confirm: true, - }, - }); - - if (newLastname) { - player.PlayerLastname = newLastname; - await Session.UpdateCompetitionPlayer( - competition.CompetitionID, - player - ).then(() => { - loadPlayers(); - }); - } - }}>{player.PlayerLastname}</td - > - <td - >{#if player.PlayerClub}{player.PlayerClub}{:else}Sans - Nom{/if}</td - > - </tr> - {/if} - {/each} - <tr id="add-player-tr"> - <td colspan="4"> - <button - on:click={async () => { - var player = Models.Player.createFrom({ - PlayerID: 65535, - PlayerFirstname: "Firstname", - PlayerLastname: "Lastname", - PlayerNationID: 0, - PlayerClubID: 0, - PlayerRegionID: 0, - PlayerInitialRank: 2, - }); - await Session.AddPlayerToCompetition( - competition.CompetitionID, - player - ).then(() => { - loadPlayers(); - }); - }} - > - Add Player - </button></td - > - </tr> - </tbody> - </table> -</div> - -<style> - .registration-container { - margin-top: 5vh; - display: flex; - flex-direction: column; - align-items: center; - } - - input { - width: 200px; - margin-bottom: 10px; - padding: 10px; - font-size: 1em; - border-radius: 10px; - border: solid 1px #003566; - } -</style> diff --git a/frontend/src/components/NewPlayerForm.svelte b/frontend/src/components/Seedings/NewPlayerForm.svelte similarity index 94% rename from frontend/src/components/NewPlayerForm.svelte rename to frontend/src/components/Seedings/NewPlayerForm.svelte index 91954998debc8ba0c35f029c273baf7e8d05abb1..d9b5db59f4eaf64d64a2a88ef7b21e8ab87cad82 100644 --- a/frontend/src/components/NewPlayerForm.svelte +++ b/frontend/src/components/Seedings/NewPlayerForm.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { onMount } from "svelte"; - import * as Models from "../../bindings/changeme/astro/services/models"; - import * as Session from "../../bindings/changeme/astro/services/session"; + import * as Models from "../../../bindings/changeme/astro/services/models"; + import * as Session from "../../../bindings/changeme/astro/services/session"; import swal from "sweetalert"; export let competition: Models.Competition; diff --git a/frontend/src/components/Seedings/Registrations.svelte b/frontend/src/components/Seedings/Registrations.svelte new file mode 100644 index 0000000000000000000000000000000000000000..7fedb50a5852caf3da510d115e519f07f71204c4 --- /dev/null +++ b/frontend/src/components/Seedings/Registrations.svelte @@ -0,0 +1,284 @@ +<script lang="ts"> + import { onMount } from "svelte"; + import * as Models from "../../../bindings/changeme/astro/services/models"; + import * as Session from "../../../bindings/changeme/astro/services/session"; + import { SelectedCompetition } from "../../store"; + import swal from "sweetalert"; + + let players: (Models.Player | null)[] = []; + let filteredPlayers: (Models.Player | null)[] = []; + let competition: Models.Competition | undefined; + + SelectedCompetition.subscribe((value) => { + competition = value; + loadPlayers(); + }); + + async function loadPlayers() { + if (competition === undefined) { + return; + } + + Session.GetAllPlayersFromCompetition(competition.CompetitionID).then( + (result) => { + players = result; + } + ); + } + + onMount(async () => { + await loadPlayers(); + }); + + function getPlayerValueByKey(player: Models.Player | null, key: string) { + if (player === null) { + return ""; + } + + switch (key) { + case "PlayerInitialRank": + return player.PlayerInitialRank; + case "PlayerFirstname": + return player.PlayerFirstname; + case "PlayerLastname": + return player.PlayerLastname; + case "PlayerClub": + return player.PlayerClub; + default: + return ""; + } + } + + let sortBy = { key: "PlayerInitialRank", asc: true }; + + $: players = players.sort((a, b) => { + let aValue = getPlayerValueByKey(a, sortBy.key); + let bValue = getPlayerValueByKey(b, sortBy.key); + if (aValue === null || bValue === null) { + return 0; + } + if (aValue < bValue) { + return sortBy.asc ? -1 : 1; + } + if (aValue > bValue) { + return sortBy.asc ? 1 : -1; + } + return 0; + }); + + let searchTerms = ""; + + $: filteredPlayers = players.filter((player) => { + if (player === null) { + return []; + } + + return ( + player.PlayerFirstname.toLowerCase().includes( + searchTerms.toLowerCase() + ) || + player.PlayerLastname.toLowerCase().includes( + searchTerms.toLowerCase() + ) || + player.PlayerClub?.club_name + .toLowerCase() + .includes(searchTerms.toLowerCase()) + ); + }); +</script> + +<div class="registration-container"> + <input + type="text" + bind:value={searchTerms} + placeholder="Search for a player" + /> + {#if competition == undefined} + <p>No competition selected</p> + {:else} + <table> + <thead> + <tr> + <th + on:click={() => { + sortBy = { + key: "PlayerInitialRank", + asc: !sortBy.asc, + }; + }}>Initial Rank</th + > + <th + on:click={() => { + sortBy = { + key: "PlayerFirstname", + asc: !sortBy.asc, + }; + }}>Firstname</th + > + <th + on:click={() => { + sortBy = { + key: "PlayerLastname", + asc: !sortBy.asc, + }; + }}>Lastname</th + > + <th + on:click={() => { + sortBy = { key: "PlayerClub", asc: !sortBy.asc }; + }}>Club</th + > + </tr> + </thead> + <tbody> + {#each filteredPlayers as player} + {#if player != null} + <tr> + <td + on:dblclick={async () => { + let newRank = await swal({ + content: { + element: "input", + attributes: { + placeholder: "New rank", + type: "number", + }, + }, + buttons: { + cancel: true, + confirm: true, + }, + }); + + if (newRank) { + player.PlayerInitialRank = + parseInt(newRank); + + if (isNaN(player.PlayerInitialRank)) { + await swal( + "Error", + "Rank must be a number", + "error" + ); + return; + } + + if (competition != undefined) + await Session.UpdateCompetitionPlayer( + competition.CompetitionID, + player + ).then(() => { + loadPlayers(); + }); + } + }}>{player.PlayerInitialRank}</td + > + <td + on:dblclick={async () => { + let newFirstname = await swal({ + content: { + element: "input", + attributes: { + placeholder: "New firstname", + }, + }, + buttons: { + cancel: true, + confirm: true, + }, + }); + + if (newFirstname) { + player.PlayerFirstname = newFirstname; + if (competition != undefined) + await Session.UpdateCompetitionPlayer( + competition.CompetitionID, + player + ).then(() => { + loadPlayers(); + }); + } + }}>{player.PlayerFirstname}</td + > + <td + on:dblclick={async () => { + let newLastname = await swal({ + content: { + element: "input", + attributes: { + placeholder: "New lastname", + }, + }, + buttons: { + cancel: true, + confirm: true, + }, + }); + + if (newLastname) { + player.PlayerLastname = newLastname; + if (competition != undefined) + await Session.UpdateCompetitionPlayer( + competition.CompetitionID, + player + ).then(() => { + loadPlayers(); + }); + } + }}>{player.PlayerLastname}</td + > + <td + >{#if player.PlayerClub}{player.PlayerClub}{:else}Sans + Nom{/if}</td + > + </tr> + {/if} + {/each} + <tr id="add-player-tr"> + <td colspan="4"> + <button + on:click={async () => { + var player = Models.Player.createFrom({ + PlayerID: 65535, + PlayerFirstname: "Firstname", + PlayerLastname: "Lastname", + PlayerNationID: 0, + PlayerClubID: 0, + PlayerRegionID: 0, + PlayerInitialRank: 2, + }); + if (competition != undefined) + await Session.AddPlayerToCompetition( + competition.CompetitionID, + player + ).then(() => { + loadPlayers(); + }); + }} + > + Add Player + </button></td + > + </tr> + </tbody> + </table> + {/if} +</div> + +<style> + .registration-container { + margin-top: 5vh; + display: flex; + flex-direction: column; + align-items: center; + } + + input { + width: 200px; + margin-bottom: 10px; + padding: 10px; + font-size: 1em; + border-radius: 10px; + border: solid 1px #003566; + } +</style> diff --git a/frontend/src/main.ts b/frontend/src/main.ts index d5f003c56fd78633b10322e3f735649e3c2867e0..a2e20e8fec959a8476acf8e8faf52f05da7c8aea 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -4,4 +4,4 @@ const app = new App({ target: document.getElementById('app')!, }) -export default app +export default app; \ No newline at end of file diff --git a/frontend/src/store.ts b/frontend/src/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..6cc9cd8e4ce7b745e6c0bcbcac72ebdc703ca042 --- /dev/null +++ b/frontend/src/store.ts @@ -0,0 +1,4 @@ +import { writable } from 'svelte/store'; +import type { Competition } from '../bindings/changeme/astro/services/models'; + +export const SelectedCompetition = writable<Competition | undefined>(undefined); \ No newline at end of file