From 625ede2d62bd3e96b6ae1cadf21a35ae6ba5bcf7 Mon Sep 17 00:00:00 2001
From: ForkBench <robinvandemerghel@protonmail.com>
Date: Thu, 11 Jul 2024 10:51:28 +0200
Subject: [PATCH] Adding paging : ability to select a competition and manage
 it.

---
 frontend/src/App.svelte                       |  20 +-
 .../src/{components => common}/NavBar.svelte  |   8 +
 frontend/src/components/Registrations.svelte  | 248 ---------------
 .../{ => Seedings}/NewPlayerForm.svelte       |   4 +-
 .../components/Seedings/Registrations.svelte  | 284 ++++++++++++++++++
 frontend/src/main.ts                          |   2 +-
 frontend/src/store.ts                         |   4 +
 7 files changed, 309 insertions(+), 261 deletions(-)
 rename frontend/src/{components => common}/NavBar.svelte (85%)
 delete mode 100644 frontend/src/components/Registrations.svelte
 rename frontend/src/components/{ => Seedings}/NewPlayerForm.svelte (94%)
 create mode 100644 frontend/src/components/Seedings/Registrations.svelte
 create mode 100644 frontend/src/store.ts

diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index 60569b2..a3c5000 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 101e278..17f3261 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 89632bd..0000000
--- 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 9195499..d9b5db5 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 0000000..7fedb50
--- /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 d5f003c..a2e20e8 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 0000000..6cc9cd8
--- /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
-- 
GitLab