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 a2b61cf2ab5c44abd1963eece22e5d09dad7b0c4
parent 893aafc6ad5f6dbb809881972bdfd168c407350b
Author: Berchtold Samuel <samuel.berchtold@gmail.com>
Date:   Wed,  9 Oct 2024 00:06:13 +0200

change low width table design to description tables

Diffstat:
Mindex.html | 4++--
Msrc/frontend/App.jsx | 4++--
Msrc/frontend/Leaderboard.jsx | 250+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/frontend/css/App.css | 73+++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/frontend/css/index.css | 8+++++++-
5 files changed, 198 insertions(+), 141 deletions(-)

diff --git a/index.html b/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> <link rel="icon" type="image/x-icon" href="/favicon.ico" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta name="viewport" content="width=device-width" /> <link rel="apple-touch-icon" href="/logo192.png" /> <!-- manifest.json provides metadata used when your web app is installed on a @@ -13,7 +13,7 @@ <title>My Heats</title> </head> <body> - <noscript>You need to enable JavaScript to run this app.</noscript> + <noscript>Why You scared of JavaScript so much, failure.</noscript> <div id="root"></div> <script type="module" src="/src/frontend/main.jsx"></script> </body> diff --git a/src/frontend/App.jsx b/src/frontend/App.jsx @@ -65,7 +65,7 @@ function Layout() { }, []) return ( - <Fragment> + <> <nav className={theme}> <ul> <li> @@ -123,7 +123,7 @@ function Layout() { } </span> </footer> - </Fragment> + </> ) } diff --git a/src/frontend/Leaderboard.jsx b/src/frontend/Leaderboard.jsx @@ -204,46 +204,51 @@ function NewHeatForm(leaderboard, rankingComp, selectHeatRef, selectRankRef, ses <p> Create new heat with top N athletes from the sorted leaderboard (<i>* required</i>). </p> - <form method='post' onSubmit={e => newHeatFromLeaderboard( - e, - leaderboard, - rankingComp, - selectHeatRef, - selectRankRef, - session, - )}> - <table> - <thead> - <tr> - <th>New heat name *</th> - <th>Location</th> - <th>Planned start</th> - <th>Include top N</th> - <td></td> - </tr> - </thead> - <tbody> - <tr> - <td data-title='New heat name *'> - <input type='text' name='name' /> - </td> - <td data-title='Location'> - <input type='text' name='location' /> - </td> - <td data-title='Planned start'> - <input - type='time' - name='planned_start' /> - </td> - <td data-title='Include top N'> - <input type='number' name='size' /> - </td> - <td> - <button type='submit'>&#43; new</button> - </td> - </tr> - </tbody> - </table> + <form + method="post" + onSubmit={(e) => + newHeatFromLeaderboard( + e, + leaderboard, + rankingComp, + selectHeatRef, + selectRankRef, + session + ) + } + > + <div className="table-container"> + <table> + <thead> + <tr> + <th>New heat name *</th> + <th>Location</th> + <th>Planned start</th> + <th>Include top N</th> + <td></td> + </tr> + </thead> + <tbody> + <tr> + <td data-title="New heat name *"> + <input type="text" name="name" /> + </td> + <td data-title="Location"> + <input type="text" name="location" /> + </td> + <td data-title="Planned start"> + <input type="time" name="planned_start" /> + </td> + <td data-title="Include top N"> + <input type="number" name="size" /> + </td> + <td> + <button type="submit">&#43; new</button> + </td> + </tr> + </tbody> + </table> + </div> </form> </div> ) @@ -351,85 +356,130 @@ function Leaderboard({session}) { }, [heatSelection]); return ( - <div> - <div className='Leaderboard'> + <> + <div className="Leaderboard"> <header> - <button disabled={!loading} className='loading' >↺ loading</button> - <button className={`show-details ${details ? 'toggled' : ''}`} onClick={() => showDetails(!details)}> - <div className='thumb'></div> - <span>{details ? 'less' : 'more'}</span> + <button disabled={!loading} className="loading"> + ↺ loading </button> - <label htmlFor='heat'>Heats to display</label> + <button + className={`show-details ${details ? "toggled" : ""}`} + onClick={() => showDetails(!details)} + > + <div className="thumb"></div> + <span>{details ? "less" : "more"}</span> + </button> + <label htmlFor="heat">Heats to display</label> <Select closeMenuOnSelect={false} isMulti options={heatOpts} - onChange={h => setHeatSelection(h)} + onChange={(h) => setHeatSelection(h)} ref={selectHeatRef} - id='heat' /> - <label htmlFor='rank' className={details ? '' : 'hidden'}>Rank by</label> - <Select + id="heat" + /> + <label htmlFor="rank" className={details ? "" : "hidden"}> + Rank by + </label> + <Select closeMenuOnSelect={false} isMulti options={rankOpts} defaultValue={rankOpts[0].options[3]} - onChange={h => setRankingComp(h)} + onChange={(h) => setRankingComp(h)} ref={selectRankRef} - className={details ? '' : 'hidden'} - id='rank' /> + className={details ? "" : "hidden"} + id="rank" + /> </header> - <table className={details ? 'leaderboard' : 'hide-rank'}> - <thead> - <tr> - <th className={details ? 'right' : 'hidden'}>Rank</th> - <th className='right'>Start Nr.</th> - <th>Name</th> - <th className={details ? '' : 'hidden'}>Birthday</th> - <th className={details ? '' : 'hidden'}>School</th> - {heatSelection.map(h => ( - <th className={details ? 'right' : 'hidden'} key={h.value}>{h.label}</th> + <div className="table-container"> + <table className={details ? "leaderboard" : "hide-rank"}> + <thead> + <tr> + <th className={details ? "right" : "hidden"}>Rank</th> + <th className="right">Start Nr.</th> + <th>Name</th> + <th className={details ? "" : "hidden"}>Birthday</th> + <th className={details ? "" : "hidden"}>School</th> + {heatSelection.map((h) => ( + <th className={details ? "right" : "hidden"} key={h.value}> + {h.label} + </th> + ))} + <th className={details ? "right" : "hidden"}>Best</th> + <th className={details ? "right" : "hidden"}>Worst</th> + <th className="right">Total</th> + </tr> + </thead> + <tbody> + {leaderboard.sort(rankByHeat(rankingComp)).map((i) => ( + <tr key={i.id}> + <td className={details ? "right" : "hidden"}></td> + <td data-title="Start Nr." className="right"> + {i.nr} + </td> + <td data-title="Name"> + {i.firstname} {i.lastname} + </td> + <td data-title="Birthday" className={details ? "" : "hidden"}> + {i.birthday + ? new Date(i.birthday).toLocaleDateString( + locale, + dateOptions + ) + : ""} + </td> + <td data-title="School" className={details ? "" : "hidden"}> + {i.school} + </td> + {heatSelection.map((h) => ( + // list all scores from the judges seperated with '+' signs, show sum on right side + <td + key={h.value} + className={details ? "right" : "hidden"} + data-title={h.label} + > + {formatScores(i, h)} + </td> + ))} + <td + className={details ? "right" : "hidden"} + data-title="Best" + > + {i.bestHeat} + </td> + <td + className={details ? "right" : "hidden"} + data-title="Worst" + > + {i.worstHeat} + </td> + <td className="right total" data-title="Total"> + {i.sum} + </td> + </tr> ))} - <th className={details ? 'right' : 'hidden'}>Best</th> - <th className={details ? 'right' : 'hidden'}>Worst</th> - <th className='right'>Total</th> - </tr> - </thead> - <tbody> - {leaderboard.sort(rankByHeat(rankingComp)).map(i => ( - <tr key={i.id}> - <td className={details ? 'right' : 'hidden'}></td> - <td data-title='Start Nr.' className='right'>{i.nr}</td> - <td data-title='Name'>{i.firstname} {i.lastname}</td> - <td data-title='Birthday' className={details ? '' : 'hidden'}> - {i.birthday ? new Date(i.birthday).toLocaleDateString(locale, dateOptions) : ''} - </td> - <td data-title='School' className={details ? '' : 'hidden'}>{i.school}</td> - {heatSelection.map(h => ( - <Fragment key={h.value}> - {/* list all scores from the judges seperated with '+' signs, show sum on right side */} - <td className={details ? 'right' : 'hidden'} data-title={h.label}>{formatScores(i, h)}</td> - </Fragment> - ))} - <td className={details ? 'right' : 'hidden'} data-title='Best'>{i.bestHeat}</td> - <td className={details ? 'right' : 'hidden'} data-title='Worst'>{i.worstHeat}</td> - <td className='right total' data-title='Total'>{i.sum}</td> - </tr> - ))} - </tbody> - </table> + </tbody> + </table> + </div> </div> <ExportForm leaderboard={leaderboard} heatSelection={heatSelection} - rankingComp={rankingComp} /> - {session.auth ? <NewHeatForm - leaderboard={leaderboard} rankingComp={rankingComp} - selectHeatRef={selectHeatRef} - selectRankRef={selectRankRef} - session={session} - /> : ''} - </div> + /> + {session.auth ? ( + <NewHeatForm + leaderboard={leaderboard} + rankingComp={rankingComp} + selectHeatRef={selectHeatRef} + selectRankRef={selectRankRef} + session={session} + /> + ) : ( + "" + )} + </> ) } diff --git a/src/frontend/css/App.css b/src/frontend/css/App.css @@ -99,10 +99,11 @@ footer span button { } .loginForm, .exportForm { - margin: 30px; + margin: auto; + padding: 3rem; } .loginForm button, .loginForm input, .exportForm button { - width: 250px; + width: 100%; display: block; } @@ -125,6 +126,11 @@ footer span button { padding: 0 20px; } +.table-container { + padding-top: 1rem; + overflow-x: auto; +} + .Scoring ul { display: flex; flex-direction: row; @@ -214,55 +220,50 @@ td.total { } /* https://css-tricks.com/making-tables-responsive-with-minimal-css */ -@media(max-width: 1100px) { +/* Mobile / Small screen styles */ +@media (max-width: 768px) { + table.leaderboard { + display: block; + } + table.leaderboard thead { - left: -9999px; - position: absolute; - visibility: hidden; + display: none; } - table.leaderboard tr { - display: flex; - flex-direction: row; - flex-wrap: wrap; - padding: 20px 0; + table.leaderboard tbody, + table.leaderboard tr, + table.leaderboard td { + display: block; } - table.leaderboard tr:not(:last-child) { - border-bottom: 1px solid #e1e1e7; + table.leaderboard tr { + padding: 2rem; } table.leaderboard td { - margin: 0 -1px -1px 0; - padding-top: 35px; - margin-bottom: 25px; + display: flex; + padding: 10px; + border-bottom: 1px solid #ddd; position: relative; - width: 35%; - text-align: left !important; } - table.leaderboard td:before { - content: attr(data-title); - position: absolute; - top: 3px; - left: 20px; + table.leaderboard:not(.hide-rank) tr td:first-child::before { + content: counter(rowNumber) ". Rank"; + } + + table.leaderboard td[data-title]:before { + content: attr(data-title) ": "; + display: inline-block; + width: 50%; + padding-right: 1rem; + text-align: right; + align-content: center; font-size: 0.8em; text-transform: uppercase; color: #b0b0b6; } - table.leaderboard td:nth-child(-n+6) { - /* background: rgb(236, 236, 236); */ - } - - table td button { - position: absolute; - bottom: 0; - height: 50px; - width: 100%; - } - - table td:empty { - display: none; + table.leaderboard tr { + border-bottom: 1px solid #ddd; } } diff --git a/src/frontend/css/index.css b/src/frontend/css/index.css @@ -1,8 +1,13 @@ * { padding: 0; margin: 0; + box-sizing: border-box; font-family: monospace; - font-size: 18px; + font-size: 1.125rem; +} + +html, body { + height: 100%; } a { @@ -21,6 +26,7 @@ button, input { background: white; border: 1px solid #f3f2f7; color: black; + white-space: nowrap; } button {