commit 8310238b465a554af4b95b3774eaf8a0b471c09b
parent e5de22b2cbc35ab64f22d39728b215813f690f36
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Sun, 2 Jul 2023 18:04:25 +0200
feat: update layout
Diffstat:
M | src/App.css | | | 142 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- |
M | src/App.jsx | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++---------------- |
M | src/Athletes.jsx | | | 36 | ++++++++++++++++++------------------ |
M | src/Auth.jsx | | | 16 | ++++++---------- |
M | src/Heats.jsx | | | 38 | +++++++++++++------------------------- |
M | src/Leaderboard.jsx | | | 119 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
M | src/Score.jsx | | | 63 | +++++++++++++++++++++++++++++++++++++++++++-------------------- |
M | src/Startlist.jsx | | | 27 | +++++++++++++-------------- |
M | src/index.css | | | 78 | ++++++++++++++++++++---------------------------------------------------------- |
9 files changed, 326 insertions(+), 256 deletions(-)
diff --git a/src/App.css b/src/App.css
@@ -1,43 +1,96 @@
+#root {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ justify-content: space-between;
+}
+
+main {
+ flex: 1;
+}
+
+nav {
+ padding: 0;
+ margin: 0 0 20px 0;
+}
+
+nav ul {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ padding: 0;
+}
+
+nav li {
+ list-style-type: none;
+ flex: 1;
+ white-space: nowrap;
+}
+
+nav li a {
+ text-transform: uppercase;
+ text-align: center;
+ font-size: 0.8em;
+ border: 5px solid white;
+ background: #f7f7f7;
+ text-decoration: none;
+ padding: 10px;
+ display: block;
+}
+nav a.active {
+ font-weight: bold;
+ color: white;
+ background: #353535;
+}
+
table {
border-collapse: collapse;
}
th, td {
- border: 1px solid black;
padding: 5px;
+ text-align: left;
+ /* border: 1px solid #e4e4e4; */
}
-.right {
- text-align: right;
+tbody tr:nth-child(even) {
+ background-color: #f7f7f7;
}
-/* set medal icon for first three rows */
-table.leaderboard tr:first-child td:first-child::after {
- content: "🥇";
+footer {
+ padding: 20px;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
}
-table.leaderboard tr:nth-child(2) td:first-child::after {
- content: "🥈";
+footer span {
+ flex: 1;
}
-table.leaderboard tr:nth-child(3) td:first-child::after {
- content: "🥉";
+footer .login {
+ text-align: right;
+}
+footer .login button {
+ width: auto;
}
-/* increment rank row number */
-table.leaderboard tr {
- counter-increment: rowNumber;
+.right {
+ text-align: right;
}
-table.leaderboard tr td:first-child::before {
- content: counter(rowNumber);
- min-width: 1em;
- margin-right: 0.5em;
+
+.scoreInput {
+ font-size: 25px;
}
-button:disabled {
- visibility: hidden;
+.loginForm {
+ margin: 30px;
+}
+.loginForm button, .loginForm input {
+ width: 250px;
+ display: block;
}
.heatInfo {
- border-left: 10px solid rgb(255, 242, 166);
+ margin-bottom: 20px;
}
.heatInfo ul {
padding: 5px;
@@ -46,53 +99,72 @@ button:disabled {
list-style: none;
}
-.newHeatForm table th {
- text-align: left;
+.newHeatForm {
+ margin-top: 20px;
}
-.newHeatForm tr, .newHeatForm th, .newHeatForm td {
- border: none;
+
+/* increment rank row number */
+table.leaderboard tr{
+ counter-increment: rowNumber;
+}
+table.leaderboard tr td:first-child::before {
+ content: counter(rowNumber);
+ min-width: 1em;
+ margin-right: 0.5em;
+}
+
+/* highlight totals */
+table.leaderboard td:last-child {
+ font-weight: bold;
}
/* https://css-tricks.com/making-tables-responsive-with-minimal-css */
@media(max-width: 800px) {
- table.leaderboard thead {
+ table thead {
left: -9999px;
position: absolute;
visibility: hidden;
}
- table.leaderboard tr {
+ table tr {
border-bottom: 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
- margin-bottom: 40px;
+ margin-bottom: 20px;
}
- table.leaderboard td {
+ table td {
margin: 0 -1px -1px 0;
padding-top: 35px;
+ margin-bottom: 25px;
position: relative;
width: 45%;
+ text-align: left !important;
}
- table.leaderboard td:before {
+ table td:before {
content: attr(data-title);
position: absolute;
top: 3px;
left: 3px;
- font-weight: bold;
font-size: 0.8em;
- color: rgb(112, 112, 112);
text-transform: uppercase;
+ background: rgb(255, 247, 208);
}
table.leaderboard td:nth-child(-n+6) {
- background-color: rgb(243, 243, 243);
+ /* background: rgb(236, 236, 236); */
+ }
+
+ table td button {
+ position: absolute;
+ bottom: 0;
+ height: 50px;
+ width: 100%;
}
- table.leaderboard td:nth-last-child(-n+3) {
- font-weight: bold;
- font-size: 1.5em;
+ table td:empty {
+ display: none;
}
}
diff --git a/src/App.jsx b/src/App.jsx
@@ -1,6 +1,6 @@
import './App.css'
-import { Suspense, lazy, useState, useEffect } from 'react'
-import { BrowserRouter as Router, Routes, Route, Outlet, Link } from 'react-router-dom'
+import { Suspense, lazy, useState, useEffect, Fragment } from 'react'
+import { BrowserRouter as Router, Routes, Route, Outlet, Link, NavLink } from 'react-router-dom'
import { supabase } from './supabaseClient'
@@ -15,25 +15,56 @@ document.title = import.meta.env.VITE_APP_DOC_TITLE ? import.meta.env.VITE_APP_D
function Layout({session}) {
return (
- <div>
+ <Fragment>
<nav>
<ul>
- <li><Link to="/">Leaderboard</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>
- {session ? <button onClick={() => supabase.auth.signOut()}> Sign out {session.user.email} </button> :
- <Link to="/auth">Login</Link>}
+ <NavLink
+ className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
+ to="/">
+ Leaderboard
+ </NavLink>
</li>
+ {session ? <li>
+ <NavLink
+ className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
+ to="/score">
+ Scoring
+ </NavLink>
+ </li> : ''}
+ {session ? <li>
+ <NavLink
+ className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
+ to="/heats">
+ Heats and Startlists
+ </NavLink>
+ </li> : ''}
+ {session ? <li>
+ <NavLink
+ className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
+ to="/athletes">
+ Athletes
+ </NavLink>
+ </li> : ''}
</ul>
</nav>
- <Outlet />
- <div>
+ <main>
+ <Outlet />
+ </main>
+ <footer>
<br />
- MyHeats <a href="https://code.in0rdr.ch/myheats/refs.html">v0.4-nightly</a>
- </div>
- </div>
+ <span className='version'>MyHeats <a href="https://code.in0rdr.ch/myheats/refs.html">v0.4-nightly</a></span>
+ <span className='login'>
+ {session ? <button onClick={() => supabase.auth.signOut()}> Sign out {session.user.email} </button> :
+ <NavLink
+ className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
+ to="/auth">
+ Login
+ </NavLink>
+ }
+ </span>
+ </footer>
+ </Fragment>
)
}
@@ -60,7 +91,7 @@ function App() {
}, [])
return (
- <div className="App">
+ <Fragment>
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
@@ -76,7 +107,7 @@ function App() {
</Routes>
</Suspense>
</Router>
- </div>
+ </Fragment>
);
}
diff --git a/src/Athletes.jsx b/src/Athletes.jsx
@@ -36,7 +36,7 @@ async function addAthlete(e) {
function defaultsSet({nr, firstname, lastname, birthday, school}) {
return (
nr === '00'
- || firstname === 'Firstname'
+ || firstname === 'Firstname *'
|| lastname === 'Lastname'
|| birthday === '0000-00-00'
|| school === 'School/team'
@@ -72,13 +72,13 @@ function AthleteForm({session}) {
return (
<div>
- <h1>Athletes <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button></h1>
+ <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button>
<form method='post' onSubmit={addAthlete}>
<table>
<thead>
<tr>
<th>Created at</th>
- <th>Nr</th>
+ <th>Start Nr.</th>
<th>Firstname</th>
<th>Lastname</th>
<th>Birthday</th>
@@ -89,35 +89,35 @@ function AthleteForm({session}) {
<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>
+ <td data-title='Created at' className='right'>{new Date(a.created_at).toLocaleString()}</td>
+ <td data-title='Start Nr.' className='right'>{a.nr}</td>
+ <td data-title='Firstname'>{a.firstname}</td>
+ <td data-title='Lastname'>{a.lastname}</td>
+ <td data-title='Birthday'>{a.birthday}</td>
+ <td data-title='School'>{a.school}</td>
+ <td><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 data-title='Start Nr. *' className='right'>
+ <input type='number' name='nr' defaultValue='00' />
</td>
- <td>
- <input type='text' name='firstname' defaultValue='Firstname' /> *
+ <td data-title='Firstname *'>
+ <input type='text' name='firstname' defaultValue='Firstname *' />
</td>
- <td>
+ <td data-title='Lastname'>
<input type='text' name='lastname' defaultValue='Lastname' />
</td>
- <td className='right'>
+ <td data-title='Birthday' className='right'>
<input
type='date'
name='birthday' />
</td>
- <td>
+ <td data-title='School'>
<input type='text' name='school' defaultValue='School/team' />
</td>
- <td className='right'>
+ <td>
<button type='submit'>➕ new</button>
</td>
</tr>
diff --git a/src/Auth.jsx b/src/Auth.jsx
@@ -14,7 +14,7 @@ function Auth() {
email: email,
options: {
shouldCreateUser: false,
- // emailRedirectTo: 'http://localhost:5173'
+ emailRedirectTo: 'http://localhost:5173'
}})
if (error) throw error
alert('Check your email for the login link!')
@@ -28,13 +28,11 @@ function Auth() {
return (
<div className="Auth">
<div>
+ <button disabled={!loading}>{loading ? ' sending magic link' : ''}</button>
<h1>Login</h1>
- <p>Sign in via magic link with your email below</p>
- {loading ? (
- 'Sending magic link...'
- ) : (
+ <p>Sign in with a magic link.</p>
+ <div className='loginForm'>
<form onSubmit={handleLogin}>
- <label htmlFor="email">Email</label>
<input
id="email"
type="email"
@@ -42,11 +40,9 @@ function Auth() {
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
- <button>
- Send magic link
- </button>
+ <button>🛸 Send magic link</button>
</form>
- )}
+ </div>
<h1>Registration</h1>
<p>No account yet? Contact event administrator to sign up.</p>
</div>
diff --git a/src/Heats.jsx b/src/Heats.jsx
@@ -1,5 +1,5 @@
import { lazy, useEffect, useState } from 'react'
-import { useNavigate, generatePath } from 'react-router-dom'
+import { generatePath, Link } from 'react-router-dom'
import { supabase } from './supabaseClient'
const Auth = lazy(() => import('./Auth'))
@@ -35,12 +35,7 @@ async function addHeat(e) {
}
export function defaultsSet({name, location}) {
- return (name === 'Name' || location === 'Location')
-}
-
-function navigateToHeat(e, n, heatId) {
- e.preventDefault()
- n(generatePath('/startlist/:heatId', {heatId:heatId}))
+ return (name === 'Name *' || location === 'Location')
}
async function deleteHeat(e, heatId, heatName) {
@@ -59,8 +54,6 @@ function HeatForm({session}) {
const [loading, setLoading] = useState(false)
const [heats, setHeats] = useState([])
- const navigate = useNavigate()
-
useEffect(() => {
(async () => {
setLoading(true)
@@ -73,7 +66,7 @@ function HeatForm({session}) {
return (
<div>
- <h1>Heats and Startlists <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button></h1>
+ <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button>
<form method='post' onSubmit={addHeat}>
<table>
<thead>
@@ -82,38 +75,33 @@ function HeatForm({session}) {
<th>Name</th>
<th>Location</th>
<th>Planned start</th>
- <th>Startlist</th>
<th>New/delete</th>
</tr>
</thead>
<tbody>
{heats.map(h => (
<tr key={h.id}>
- <td className='right'>{new Date(h.created_at).toLocaleString()}</td>
- <td>{h.name}</td>
- <td>{h.location}</td>
- <td className='right'>{h.planned_start}</td>
- <td className='right'>
- <button onClick={e => navigateToHeat(e, navigate, h.id)}>🏁 startlist</button>
- </td>
- <td className='right'><button onClick={e => deleteHeat(e, h.id, h.name)}>🗑️ delete</button></td>
+ <td data-title='Created at' className='right'>{new Date(h.created_at).toLocaleString()}</td>
+ <td data-title='Name'><Link to={generatePath('/startlist/:heatId', {heatId:h.id})}>{h.name}</Link></td>
+ <td data-title='Location'>{h.location}</td>
+ <td data-title='Planned start' className='right'>{h.planned_start}</td>
+ <td><button onClick={e => deleteHeat(e, h.id, h.name)}>🗑️ delete</button></td>
</tr>
))}
<tr>
<td className='right'><i>* required</i></td>
- <td>
- <input type='text' name='name' defaultValue='Name' /> *
+ <td data-title='Name'>
+ <input type='text' name='name' defaultValue='Name *' />
</td>
- <td>
+ <td data-title='Location'>
<input type='text' name='location' defaultValue='Location' />
</td>
- <td className='right'>
+ <td data-title='Planned start' className='right'>
<input
type='time'
name='planned_start' />
</td>
- <td></td>
- <td className='right'>
+ <td>
<button type='submit'>➕ new</button>
</td>
</tr>
diff --git a/src/Leaderboard.jsx b/src/Leaderboard.jsx
@@ -94,6 +94,11 @@ function rankByHeat(rankingComp) {
}
}
+function getScores(i, h) {
+ const scores = i.heats.find(heat => heat.heatId === h.value)?.scores?.map(s => s.score).join(" + ")
+ return scores === "" ? 0 : scores
+}
+
async function newHeatFromLeaderboard(e, {leaderboard, rankingComp, selectHeatRef, selectRankRef}) {
e.preventDefault()
@@ -148,12 +153,9 @@ async function newHeatFromLeaderboard(e, {leaderboard, rankingComp, selectHeatRe
function NewHeatForm(leaderboard, rankingComp, selectHeatRef, selectRankRef) {
return (
<div className='newHeatForm'>
- <h2>New Heat from Leaderboard</h2>
- <p>
- Create new heat from the sorted leaderboard above.
- </p>
+ <h2>New Heat from top N</h2>
<p>
- Includes favorite "top N" athletes in the next 🔥 heat.
+ Create new heat with top N athletes from the sorted leaderboard (<i>* required</i>).
</p>
<form method='post' onSubmit={e => newHeatFromLeaderboard(
e,
@@ -163,35 +165,31 @@ function NewHeatForm(leaderboard, rankingComp, selectHeatRef, selectRankRef) {
selectRankRef
)}>
<table>
- <tbody>
+ <thead>
<tr>
- <th>New heat name: *</th>
- <td>
- <input type='text' name='name' defaultValue='Name' />
- </td>
+ <th>New heat name</th>
+ <th>Location</th>
+ <th>Planned start</th>
+ <th>Include top N</th>
+ <td></td>
</tr>
+ </thead>
+ <tbody>
<tr>
- <th>Location:</th>
- <td>
+ <td data-title='New heat name'>
+ <input type='text' name='name' defaultValue='Name *' />
+ </td>
+ <td data-title='Location'>
<input type='text' name='location' defaultValue='Location' />
</td>
- </tr>
- <tr>
- <th>Planned start:</th>
- <td>
+ <td data-title='Planned start'>
<input
type='time'
name='planned_start' />
</td>
- </tr>
- <tr>
- <th>Include top N:</th>
- <td>
+ <td data-title='Include top N'>
<input type='number' name='size' defaultValue='10' />
</td>
- </tr>
- <tr>
- <td>(<i>* required</i>)</td>
<td>
<button type='submit'>➕ new</button>
</td>
@@ -306,27 +304,39 @@ function Leaderboard({session}) {
return (
<div>
+ <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button>
<div className='Leaderboard'>
<header>
- <div>
- Heats to display:
- <Select
- closeMenuOnSelect={false}
- isMulti
- options={heatOpts}
- onChange={h => setHeatSelection(h)}
- ref={selectHeatRef}
- />
- Rank by (in this order):
- <Select
- closeMenuOnSelect={false}
- isMulti
- options={rankOpts}
- onChange={h => setRankingComp(h)}
- ref={selectRankRef}
- />
- </div>
- <h1>Leaderboard <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button></h1>
+ <table>
+ <thead>
+ <tr>
+ <th>Heats to display</th>
+ <th>Rank by</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td data-title='Heats to display'>
+ <Select
+ closeMenuOnSelect={false}
+ isMulti
+ options={heatOpts}
+ onChange={h => setHeatSelection(h)}
+ ref={selectHeatRef}
+ />
+ </td>
+ <td data-title='Rank by'>
+ <Select
+ closeMenuOnSelect={false}
+ isMulti
+ options={rankOpts}
+ onChange={h => setRankingComp(h)}
+ ref={selectRankRef}
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
</header>
<table className='leaderboard'>
<thead>
@@ -337,21 +347,11 @@ function Leaderboard({session}) {
<th>Lastname</th>
<th>Birthday</th>
<th>School</th>
- <th colSpan={heatSelection.length * 2}>Heat Scores & Sum</th>
- <th colSpan={3}>Summary (all heats)</th>
- </tr>
- <tr>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
{heatSelection.map(h => (
- <th key={h.value} colSpan={2}>{h.label}</th>
+ <th key={h.value}>{h.label}</th>
))}
- <th>👍 Best</th>
- <th>👎 Worst</th>
+ <th>Best</th>
+ <th>Worst</th>
<th>Total</th>
</tr>
</thead>
@@ -366,13 +366,12 @@ function Leaderboard({session}) {
<td data-title='School'>{i.school}</td>
{heatSelection.map(h => (
<Fragment key={h.value}>
- <td data-title={'Scores ' + h.label} className='right'>{i.heats.find(heat => heat.heatId === h.value)?.scores?.map(s => s.score).join(" / ")}</td>
- <td data-title='Sum' className='right'>{i.heats.find(heat => heat.heatId === h.value)?.summary}</td>
+ <td className='right' data-title={h.label}>{getScores(i, h)} = {i.heats.find(heat => heat.heatId === h.value)?.summary}</td>
</Fragment>
))}
- <td data-title='👍 Best' className='right'>{i.bestHeat}</td>
- <td data-title='👎 Worst' className='right'>{i.worstHeat}</td>
- <td data-title='Total' className='right'>{i.sum}</td>
+ <td className='right' data-title='Best'>{i.bestHeat}</td>
+ <td className='right' data-title='Worst'>{i.worstHeat}</td>
+ <td className='right' data-title='Total'>{i.sum}</td>
</tr>
))}
</tbody>
diff --git a/src/Score.jsx b/src/Score.jsx
@@ -20,6 +20,7 @@ function ScoringForm({session}) {
const [athleteOpts, setAthleteOpts] = useState([])
const [athleteSelection, setAthleteSelection] = useState(0)
const [score, setScore] = useState(0)
+ const [loading, setLoading] = useState(false)
// add options to select or rank by heat
const heatOpts = heats.map(h => {
@@ -31,13 +32,16 @@ function ScoringForm({session}) {
useEffect(() => {
(async () => {
+ setLoading(true)
const heatList = await supabase.from('heats').select()
setHeats(heatList.data)
const startlist = await getStartlistForHeats([heatSelection.value])
- if (startlist.error)
+ if (startlist.error) {
+ setLoading(false)
return
+ }
setAthleteOpts(startlist.data.map(s => {
return {
@@ -46,8 +50,10 @@ function ScoringForm({session}) {
}
}))
- if (heatSelection.value === undefined || athleteSelection.value === undefined)
+ if (heatSelection.value === undefined || athleteSelection.value === undefined) {
+ setLoading(false)
return
+ }
// check if existing score for heat and athlete exists
const currentScore = await supabase.from('scores').select()
@@ -65,29 +71,46 @@ function ScoringForm({session}) {
athleteSelection.value,
session.user.id)
}
+ setLoading(false)
})();
}, [heatSelection, athleteSelection, session.user.id, score]);
return (
<div>
- <h1>Score Athletes</h1>
- Heat:
- <Select
- options={heatOpts}
- onChange={h => { setHeatSelection(h); setScore(0) }}
- />
- Athlete:
- <Select
- options={athleteOpts}
- onChange={a => { setAthleteSelection(a); setScore(0) }}
- />
- Score:
- <input
- type="number"
- size="5"
- value={score}
- onChange={(e) => setScore(e.target.value)}
- />
+ <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button>
+ <table>
+ <thead>
+ <tr>
+ <th>Heat</th>
+ <th>Athlete</th>
+ <th>Score</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td data-title='Heat'>
+ <Select
+ options={heatOpts}
+ onChange={h => { setHeatSelection(h); setScore(0) }}
+ />
+ </td>
+ <td data-title='Athlete'>
+ <Select
+ options={athleteOpts}
+ onChange={a => { setAthleteSelection(a); setScore(0) }}
+ />
+ </td>
+ <td data-title='Score'>
+ <input
+ className='scoreInput'
+ type="number"
+ value={score}
+ onChange={(e) => setScore(e.target.value)}
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
</div>
)
}
diff --git a/src/Startlist.jsx b/src/Startlist.jsx
@@ -92,17 +92,18 @@ function StartlistForm({heatId}) {
return (
<div>
- <h1>Startlist #{heatId} {heatName} <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button></h1>
+ <button disabled={!loading}>{loading ? '🔄 loading' : ''}</button>
+ <h1>Startlist #{heatId} {heatName}</h1>
<div className='heatInfo'>
<ul>
- <li><b>Location:</b>   {heatLocation ? heatLocation : 'n/a'}</li>
- <li><b>Planned start:</b> {heatStart ? heatStart : 'n/a'}</li>
+ <li><b>Location:</b> {heatLocation ? heatLocation : 'n/a'}</li>
+ <li><b>Planned start:</b> {heatStart ? heatStart : 'n/a'}</li>
</ul>
</div>
<table>
<thead>
<tr>
- <th>Nr</th>
+ <th>Start Nr.</th>
<th>Firstname</th>
<th>Lastname</th>
<th>Birthday</th>
@@ -113,12 +114,12 @@ function StartlistForm({heatId}) {
<tbody>
{startlist.map(i => (
<tr key={i.id}>
- <td className='right'>{i.athlete.nr}</td>
- <td>{i.athlete.firstname}</td>
- <td>{i.athlete.lastname}</td>
- <td className='right'>{i.athlete.birthday}</td>
- <td>{i.athlete.school}</td>
- <td className='right'><button onClick={e => removeAthleteFromHeat(
+ <td data-title='Start Nr.' className='right'>{i.athlete.nr}</td>
+ <td data-title='Firstname'>{i.athlete.firstname}</td>
+ <td data-title='Lastname'>{i.athlete.lastname}</td>
+ <td data-title='Birthday'>{i.athlete.birthday}</td>
+ <td data-title='School'>{i.athlete.school}</td>
+ <td><button onClick={e => removeAthleteFromHeat(
e,
i.id,
i.athlete.firstname,
@@ -128,15 +129,13 @@ function StartlistForm({heatId}) {
</tr>
))}
<tr>
- <td></td>
- <td colSpan={2}>
+ <td data-title='Athlete' colSpan={5}>
<Select
options={athleteOpts}
onChange={(e) => setselectedAthlete(e)}
/>
</td>
- <td colSpan={2}></td>
- <td className='right'>
+ <td>
<button onClick={(e) => addAthleteToHeat(e, selectedAthlete, heatId)}>➕ add</button>
</td>
</tr>
diff --git a/src/index.css b/src/index.css
@@ -1,69 +1,31 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- -webkit-text-size-adjust: 100%;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
+* {
+ padding: 0;
margin: 0;
- display: flex;
- /* place-items: center; */
- min-width: 320px;
- min-height: 100vh;
+ font-family: monospace;
+ font-size: 18px;
}
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
+a {
+ color: black;
}
button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
+ background: none;
+ border: none;
cursor: pointer;
- transition: border-color 0.25s;
+ text-align: left;
+ box-shadow: 0 1px #c7c7c7;
+ background: white;
+ border: 1px solid #f7f7f7;
+ padding: 5px 10px;
+ width: 100%;
+ text-align: center;
}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
+
+button:disabled {
+ visibility: hidden;
}
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
+input {
+ width: 100%;
}