myheats

Live heats, scoring and leaderboard for sport events
git clone https://git.in0rdr.ch/myheats.git
Log | Files | Refs | Pull requests | README | LICENSE

commit 077905c2a3350fbbd8b78670f35ec56fcac201ee
parent cd3c28307d376295499eb4e30a2d1a348a553ca9
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Fri,  7 Apr 2023 19:16:58 +0200

feat: add athletes to startlist

Diffstat:
Mschema/startlist.sql | 6+++---
Msrc/Athletes.js | 100++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/Heats.js | 4++--
Msrc/Startlist.js | 60++++++++++++++++++++++++++++++++++++++++++++++++++----------
4 files changed, 105 insertions(+), 65 deletions(-)

diff --git a/schema/startlist.sql b/schema/startlist.sql @@ -27,8 +27,8 @@ SET default_table_access_method = heap; CREATE TABLE public.startlist ( id bigint NOT NULL, created_at timestamp with time zone DEFAULT now(), - heat bigint, - athlete bigint + heat bigint NOT NULL, + athlete bigint NOT NULL ); @@ -60,7 +60,7 @@ ALTER TABLE public.startlist ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTIT -- ALTER TABLE ONLY public.startlist - ADD CONSTRAINT startlist_pkey PRIMARY KEY (id); + ADD CONSTRAINT startlist_pkey PRIMARY KEY (heat, athlete); -- diff --git a/src/Athletes.js b/src/Athletes.js @@ -80,59 +80,59 @@ function AthleteForm({session}) { return ( <div> <h1>Athletes <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button></h1> - <form method="post" onSubmit={addAthlete}> - <table> - <thead> - <tr> - <th>Created at</th> - <th>Nr</th> - <th>Firstname</th> - <th>Lastname</th> - <th>Birthday</th> - <th>School</th> - <th>Delete</th> - </tr> - </thead> - <tbody> - {athletes.map(a => ( - <tr key={a.id}> - <td className='right'>{new Date(a.created_at).toLocaleString()}</td> - <td className='right'>{a.nr}</td> - <td>{a.firstname}</td> - <td>{a.lastname}</td> - <td className='right'>{a.birthday}</td> - <td>{a.school}</td> - <td className='right'><button onClick={e => deleteAthlete(e, a.id, a.firstname, a.lastname)}>🗑️</button></td> + <form method='post' onSubmit={addAthlete}> + <table> + <thead> + <tr> + <th>Created at</th> + <th>Nr</th> + <th>Firstname</th> + <th>Lastname</th> + <th>Birthday</th> + <th>School</th> + <th>New/delete</th> </tr> - ))} - <tr> - <td className='right'><i>* required</i></td> - <td className='right'> - <input type='number' name='nr' defaultValue='00' /> * + </thead> + <tbody> + {athletes.map(a => ( + <tr key={a.id}> + <td className='right'>{new Date(a.created_at).toLocaleString()}</td> + <td className='right'>{a.nr}</td> + <td>{a.firstname}</td> + <td>{a.lastname}</td> + <td className='right'>{a.birthday}</td> + <td>{a.school}</td> + <td className='right'><button onClick={e => deleteAthlete(e, a.id, a.firstname, a.lastname)}>🗑️ delete</button></td> + </tr> + ))} + <tr> + <td className='right'><i>* required</i></td> + <td className='right'> + <input type='number' name='nr' defaultValue='00' /> * </td> - <td> - <input type='text' name='firstname' defaultValue='Firstname' /> * - </td> - <td> - <input type='text' name='lastname' defaultValue='Lastname' /> + <td> + <input type='text' name='firstname' defaultValue='Firstname' /> * </td> - <td className='right'> - <input - type='text' - name='birthday' - defaultValue='0000-00-00' - pattern='(\d{4}-\d{2}-\d{2})*' - onBlur={(e) => validateBirthday(e)} /> - </td> - <td> - <input type='text' name='school' defaultValue='School/team' /> - </td> - <td className='right'> - <button type='submit'>➕ add</button> - </td> - </tr> - </tbody> - </table> + <td> + <input type='text' name='lastname' defaultValue='Lastname' /> + </td> + <td className='right'> + <input + type='text' + name='birthday' + defaultValue='0000-00-00' + pattern='(\d{4}-\d{2}-\d{2})*' + onBlur={(e) => validateBirthday(e)} /> + </td> + <td> + <input type='text' name='school' defaultValue='School/team' /> + </td> + <td className='right'> + <button type='submit'>➕ new</button> + </td> + </tr> + </tbody> + </table> </form> </div> ) diff --git a/src/Heats.js b/src/Heats.js @@ -54,9 +54,9 @@ function HeatForm({session}) { <td>{h.location}</td> <td className='right'>{h.planned_start}</td> <td className='right'> - <button onClick={e => navigate(generatePath('/startlist/:heatId', {heatId:h.id}))}>🏁</button> + <button onClick={e => navigate(generatePath('/startlist/:heatId', {heatId:h.id}))}>🏁 startlist</button> </td> - <td className='right'><button onClick={e => deleteHeat(e, h.id, h.name)}>🗑️</button></td> + <td className='right'><button onClick={e => deleteHeat(e, h.id, h.name)}>🗑️ delete</button></td> </tr> ))} </tbody> diff --git a/src/Startlist.js b/src/Startlist.js @@ -1,17 +1,27 @@ import { lazy, useEffect, useState } from 'react' import { useParams } from 'react-router-dom' +import Select from 'react-select' import { supabase } from './supabaseClient' -import { hasSelectionSupport } from '@testing-library/user-event/dist/utils' const Auth = lazy(() => import('./Auth')) +async function addAthleteToHeat(e, selectedAthlete, heatId) { + e.preventDefault() + + const { error } = await supabase.from('startlist').upsert({ + athlete: selectedAthlete.value, + heat: heatId + }) + + if (error === null) + window.location.reload() +} + async function removeAthleteFromHeat(e, startlistId, athleteFirstName, athleteLastName, heatName) { e.preventDefault() const athleteName = athleteFirstName + (athleteLastName ? ' ' + athleteLastName : '') - if (window.confirm( - 'Do you really want to remove athlete "' + athleteName + '" from heat "' + heatName + '"?' - )) { + if (window.confirm('Do you really want to remove athlete "' + athleteName + '" from heat "' + heatName + '"?')) { await supabase .from('startlist') .delete() @@ -24,19 +34,23 @@ function StartlistForm({heatId}) { const [heatName, setheatName] = useState("") const [heatLocation, setheatLocation] = useState("") const [heatStart, setheatStart] = useState("") + const [startlist, setStartlist] = useState([]) + const [athleteOpts, setAthleteOpts] = useState([]) + const [selectedAthlete, setselectedAthlete] = useState({}) const [loading, setLoading] = useState(false) - const [startlist, setStartlist] = useState([]) useEffect(() => { (async () => { setLoading(true) + const startlistData = await supabase .from('startlist') .select(` id, heat, athlete ( + id, nr, firstname, lastname, @@ -44,19 +58,34 @@ function StartlistForm({heatId}) { school ) `) - .eq('heat', heatId) + .eq('heat', heatId) + if (startlistData.error === null) setStartlist(startlistData.data) + const heatData = await supabase .from('heats') .select('name, location, planned_start') .eq('id', heatId) .single() + if (heatData.error === null) { setheatName(heatData.data.name) setheatLocation(heatData.data.location) setheatStart(heatData.data.planned_start) } + + const athleteList = await supabase.from('athletes').select() + if (athleteList.error === null) { + let options = athleteList.data.map(a => { + return { + value: a.id, + label: a.nr + " " + a.firstname + " " + (a.lastname ? a.lastname : "") + } + }) + setAthleteOpts(options) + } + setLoading(false) })(); }, [heatId]) @@ -70,7 +99,6 @@ function StartlistForm({heatId}) { <li><b>Planned start:</b>&emsp;{heatStart ? heatStart : 'n/a'}</li> </ul> </div> - {/* <form method="post"> */} <table> <thead> <tr> @@ -79,7 +107,7 @@ function StartlistForm({heatId}) { <th>Lastname</th> <th>Birthday</th> <th>School</th> - <th>Remove from heat</th> + <th>Add/remove</th> </tr> </thead> <tbody> @@ -96,12 +124,24 @@ function StartlistForm({heatId}) { i.athlete.firstname, i.athlete.lastname, heatName - )}>🗑️</button></td> + )}>🗑️ remove</button></td> </tr> ))} + <tr> + <td></td> + <td colSpan={2}> + <Select + options={athleteOpts} + onChange={(e) => setselectedAthlete(e)} + /> + </td> + <td colSpan={2}></td> + <td className='right'> + <button onClick={(e) => addAthleteToHeat(e, selectedAthlete, heatId)}>➕ add</button> + </td> + </tr> </tbody> </table> - {/* </form> */} </div> ) }