commit 3827101ca35316504f31a8770643852546abf714
parent a067af77aff2c3ccf9ded1a72fc8e453d65b2424
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Thu, 13 Apr 2023 23:25:27 +0200
feat: replace rating w/ scoring
Diffstat:
6 files changed, 110 insertions(+), 112 deletions(-)
diff --git a/README.md b/README.md
@@ -100,7 +100,7 @@ The basic code for the authentication flow was taken from the [official React Ge
Sign up process for new judges:
* The judge is required to have a valid email address
-* Judges can be invied to the rating backend by invite only (Click ["Invite"](https://app.supabase.com/project/_/auth/users) in Supabase backend)
+* Judges can be invied to the scoring backend by invite only (Click ["Invite"](https://app.supabase.com/project/_/auth/users) in Supabase backend)
* The UUID is referenced automatically in the `judges` table, row can be amended with name of the judge
Sign in process:
@@ -125,9 +125,7 @@ To export data to local csv:
```
## TODO
-
-* Rating UI
-* Delete/Edit heats
+* Bulk import/export in UI
## Contributors
All source code is available in this repository. Contributions are always welcome!
diff --git a/src/App.js b/src/App.js
@@ -4,7 +4,7 @@ import { BrowserRouter as Router, Routes, Route, Outlet, Link } from 'react-rout
import { supabase } from './supabaseClient'
-const Rate = lazy(() => import('./Rate'))
+const Score = lazy(() => import('./Score'))
const Heats = lazy(() => import('./Heats'))
const Athletes = lazy(() => import('./Athletes'))
const Startlist = lazy(() => import('./Startlist'))
@@ -19,7 +19,7 @@ function Layout({session}) {
<nav>
<ul>
<li><Link to="/">Leaderboard</Link></li>
- {session ? <li><Link to="/rate">Rate</Link></li> : ''}
+ {session ? <li><Link to="/score">Scoring</Link></li> : ''}
{session ? <li><Link to="/heats">Heats and Startlists</Link></li> : ''}
{session ? <li> <Link to="/athletes">Athletes</Link></li> : ''}
<li>
@@ -66,7 +66,7 @@ function App() {
<Routes>
<Route path="/" element={<Layout session={session} />}>
<Route path="/" element={<Leaderboard session={session} />} />
- <Route path="/rate" element={<Rate session={session} />} />
+ <Route path="/score" element={<Score session={session} />} />
<Route path="/heats" element={<Heats session={session} />} />
<Route path="/athletes" element={<Athletes session={session} />} />
<Route path="/startlist/:heatId" element={<Startlist session={session} />} />
diff --git a/src/Heats.js b/src/Heats.js
@@ -73,7 +73,7 @@ function HeatForm({session}) {
return (
<div>
- <h1>Heats <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button></h1>
+ <h1>Heats and Startlists <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button></h1>
<form method='post' onSubmit={addHeat}>
<table>
<thead>
diff --git a/src/Leaderboard.js b/src/Leaderboard.js
@@ -337,7 +337,7 @@ function Leaderboard({session}) {
<th>Lastname</th>
<th>Birthday</th>
<th>School</th>
- <th colSpan={heatSelection.length * 2}>Heat Ratings & Sum</th>
+ <th colSpan={heatSelection.length * 2}>Heat Scores & Sum</th>
<th colSpan={3}>Summary (all heats)</th>
</tr>
<tr>
diff --git a/src/Rate.js b/src/Rate.js
@@ -1,103 +0,0 @@
-import { lazy, useEffect, useState } from 'react'
-import { getStartlistForHeats } from './Leaderboard'
-import Select from 'react-select'
-import { supabase } from './supabaseClient'
-
-const Auth = lazy(() => import('./Auth'))
-
-async function updateRating(rating, heatId, athleteId, userId) {
- await supabase.from('ratings').upsert({
- rating: rating,
- heat: heatId,
- athlete: athleteId,
- judge: userId
- })
-}
-
-function RatingForm({session}) {
- const [heats, setHeats] = useState([])
- const [heatSelection, setHeatSelection] = useState(0)
- const [athleteOpts, setAthleteOpts] = useState([])
- const [athleteSelection, setAthleteSelection] = useState(0)
- const [rating, setRating] = useState(0)
-
- // add options to select or rank by heat
- const heatOpts = heats.map(h => {
- return {
- value: h.id,
- label: h.name
- }
- })
-
- useEffect(() => {
- (async () => {
- const heatList = await supabase.from('heats').select()
- setHeats(heatList.data)
-
- const startlist = await getStartlistForHeats([heatSelection.value])
-
- if (startlist.error)
- return
-
- setAthleteOpts(startlist.data.map(s => {
- return {
- value: s.athlete,
- label: s.nr + " " + s.firstname + " " + (s.lastname ? s.lastname : "")
- }
- }))
-
- if (heatSelection.value === undefined || athleteSelection.value === undefined)
- return
-
- // check if existing rating for heat and athlete exists
- const currentRating = await supabase.from('ratings').select()
- .eq('heat', heatSelection.value)
- .eq('athlete', athleteSelection.value)
- .eq('judge', session.user.id)
-
- if (rating === 0 && currentRating.data?.length > 0) {
- // fallback to current rating when no rating was given
- setRating(currentRating.data[0].rating)
- } else {
- // store new rating
- updateRating(rating,
- heatSelection.value,
- athleteSelection.value,
- session.user.id)
- }
- })();
- }, [heatSelection, athleteSelection, session.user.id, rating]);
-
- return (
- <div>
- <h1>Rate Athletes</h1>
- Heat:
- <Select
- options={heatOpts}
- onChange={h => { setHeatSelection(h); setRating(0) }}
- />
- Athlete:
- <Select
- options={athleteOpts}
- onChange={a => { setAthleteSelection(a); setRating(0) }}
- />
- Rating:
- <input
- type="number"
- size="5"
- value={rating}
- onChange={(e) => setRating(e.target.value)}
- />
- </div>
- )
-}
-
-function Rate({session}) {
- return (
- <div>
- {!session ? <Auth /> : <RatingForm session={session} />}
- </div>
- )
-}
-
-export default Rate
diff --git a/src/Score.js b/src/Score.js
@@ -0,0 +1,103 @@
+import { lazy, useEffect, useState } from 'react'
+import { getStartlistForHeats } from './Leaderboard'
+import Select from 'react-select'
+import { supabase } from './supabaseClient'
+
+const Auth = lazy(() => import('./Auth'))
+
+async function updateRating(rating, heatId, athleteId, userId) {
+ await supabase.from('ratings').upsert({
+ rating: rating,
+ heat: heatId,
+ athlete: athleteId,
+ judge: userId
+ })
+}
+
+function ScoringForm({session}) {
+ const [heats, setHeats] = useState([])
+ const [heatSelection, setHeatSelection] = useState(0)
+ const [athleteOpts, setAthleteOpts] = useState([])
+ const [athleteSelection, setAthleteSelection] = useState(0)
+ const [rating, setRating] = useState(0)
+
+ // add options to select or rank by heat
+ const heatOpts = heats.map(h => {
+ return {
+ value: h.id,
+ label: h.name
+ }
+ })
+
+ useEffect(() => {
+ (async () => {
+ const heatList = await supabase.from('heats').select()
+ setHeats(heatList.data)
+
+ const startlist = await getStartlistForHeats([heatSelection.value])
+
+ if (startlist.error)
+ return
+
+ setAthleteOpts(startlist.data.map(s => {
+ return {
+ value: s.athlete,
+ label: s.nr + " " + s.firstname + " " + (s.lastname ? s.lastname : "")
+ }
+ }))
+
+ if (heatSelection.value === undefined || athleteSelection.value === undefined)
+ return
+
+ // check if existing rating for heat and athlete exists
+ const currentRating = await supabase.from('ratings').select()
+ .eq('heat', heatSelection.value)
+ .eq('athlete', athleteSelection.value)
+ .eq('judge', session.user.id)
+
+ if (rating === 0 && currentRating.data?.length > 0) {
+ // fallback to current rating when no rating was given
+ setRating(currentRating.data[0].rating)
+ } else {
+ // store new rating
+ updateRating(rating,
+ heatSelection.value,
+ athleteSelection.value,
+ session.user.id)
+ }
+ })();
+ }, [heatSelection, athleteSelection, session.user.id, rating]);
+
+ return (
+ <div>
+ <h1>Score Athletes</h1>
+ Heat:
+ <Select
+ options={heatOpts}
+ onChange={h => { setHeatSelection(h); setRating(0) }}
+ />
+ Athlete:
+ <Select
+ options={athleteOpts}
+ onChange={a => { setAthleteSelection(a); setRating(0) }}
+ />
+ Score:
+ <input
+ type="number"
+ size="5"
+ value={rating}
+ onChange={(e) => setRating(e.target.value)}
+ />
+ </div>
+ )
+}
+
+function Score({session}) {
+ return (
+ <div>
+ {!session ? <Auth /> : <ScoringForm session={session} />}
+ </div>
+ )
+}
+
+export default Score