myheats

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

Heats.jsx (6747B)


      1 import { lazy, useEffect, useState } from 'react'
      2 import { generatePath, Link } from 'react-router-dom'
      3 import { exportHeatsToCSV } from './utils'
      4 
      5 const api_uri = import.meta.env.VITE_API_URI
      6 const api_port = import.meta.env.VITE_API_PORT
      7 const locale = import.meta.env.VITE_LOCALE
      8 
      9 const Auth = lazy(() => import('./Auth'))
     10 
     11 async function changeVisibility(e, heatId, heatName, session) {
     12   e.preventDefault()
     13 
     14   if (window.confirm('Change visibility of heat "' + heatName + '"?')) {
     15     const { data, error } = await toggleVisibility(heatId, session)
     16     if (error === undefined) {
     17       window.location.reload()
     18     } else {
     19       console.error(error)
     20     }
     21   }
     22 }
     23 
     24 async function toggleVisibility(heatId, session) {
     25   try {
     26     const res = await fetch(`${api_uri}:${api_port}/v1/leaderboard/toggleHeatVisibility`, {
     27       method: 'POST',
     28       headers: {
     29               'Content-Type': 'application/json',
     30               'Authorization': `Bearer ${session.auth.token}`,
     31       },
     32       body: JSON.stringify({
     33         "heat_id": heatId
     34       }),
     35     })
     36     const { data, error } = await res.json()
     37     return data
     38   } catch (error) {
     39     throw(error)
     40   }
     41 }
     42 
     43 export async function addNewHeat(name, heatLocation, plannedStart, privateHeat, session) {
     44   try {
     45     const res = await fetch(`${api_uri}:${api_port}/v1/leaderboard/newHeat`, {
     46       method: 'POST',
     47       headers: {
     48               'Content-Type': 'application/json',
     49               'Authorization': `Bearer ${session.auth.token}`,
     50       },
     51       body: JSON.stringify({
     52         "name": name,
     53         "location": heatLocation,
     54         "planned_start": plannedStart,
     55         "private": privateHeat
     56       }),
     57     })
     58     const { data, error } = await res.json()
     59     return data
     60   } catch (error) {
     61     throw(error)
     62   }
     63 }
     64 
     65 async function addHeat(e, session) {
     66   e.preventDefault()
     67 
     68   // Read the form data
     69   const formData = new FormData(e.target);
     70   const formJson = Object.fromEntries(formData.entries());
     71 
     72   // create new heat
     73   try {
     74     const heat = await addNewHeat(
     75       formJson.name,
     76       formJson.location,
     77       // planned_start is an empty string if unset
     78       // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/time
     79       formJson.planned_start === '' ? null : formJson.planned_start,
     80       // the default checkbox value is 'on'
     81       // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/checkbox
     82       formJson.private === 'on' ? true : false,
     83 	    session
     84     )
     85     window.location.reload()
     86   } catch (error) {
     87     console.error('Failed to create new heat: ' + error.message)
     88   }
     89 }
     90 
     91 export async function removeHeat(heatId, session) {
     92   const res = await fetch(`${api_uri}:${api_port}/v1/leaderboard/removeHeat`, {
     93     method: 'POST',
     94     headers: {
     95             'Content-Type': 'application/json',
     96             'Authorization': `Bearer ${session.auth.token}`,
     97     },
     98     body: JSON.stringify({
     99       "heat_id": heatId
    100     }),
    101   })
    102   return await res.json()
    103 }
    104 
    105 async function deleteHeat(e, heatId, heatName, session) {
    106   e.preventDefault()
    107 
    108   if (window.confirm('Do you really want to delete heat "' + heatName + '"?')) {
    109     const { data, error } = await removeHeat(heatId, session)
    110     if (error === undefined) {
    111       window.location.reload()
    112     } else {
    113       console.error(error)
    114     }
    115   }
    116 }
    117 
    118 // export heats
    119 function ExportForm(heats) {
    120   return (
    121     <div className='exportForm'>
    122       <form method='post' onSubmit={e => exportHeatsToCSV(e, heats)}>
    123         <button type='submit'>&#9663; export</button>
    124       </form>
    125     </div>
    126   )
    127 }
    128 
    129 function HeatForm({session}) {
    130   const [loading, setLoading] = useState(false)
    131   const [privateHeatChecked, setPrivateHeatChecked] = useState(true);
    132   const [heats, setHeats] = useState([])
    133   const dateOptions = {
    134             year: "numeric",
    135             month: "2-digit",
    136             day: "2-digit",
    137         }
    138 
    139   useEffect(() => {
    140     (async () => {
    141       setLoading(true)
    142       const res = await fetch(`${api_uri}:${api_port}/v1/leaderboard/allHeats`, {
    143         headers: {
    144                 'Content-Type': 'application/json',
    145                 'Authorization': `Bearer ${session.auth.token}`,
    146         }
    147       })
    148       const { data, error } = await res.json()
    149       if (error) {
    150         console.error(error)
    151       }
    152       setHeats(data)
    153       setLoading(false)
    154     })();
    155   }, [])
    156 
    157   return (
    158     <div className='HeatForm'>
    159       <button disabled={!loading} className='loading'>↺ loading</button>
    160       <form method='post' onSubmit={e => addHeat(e, session)}>
    161         <table>
    162           <thead>
    163             <tr>
    164               <th>Created at</th>
    165               <th>Name *</th>
    166               <th>Location</th>
    167               <th>Planned start</th>
    168               <th>Private</th>
    169               <th>New/delete</th>
    170             </tr>
    171           </thead>
    172           <tbody>
    173             {heats.map(h => (
    174               <tr key={h.id}>
    175                 <td data-title='Created at' className='right'>{new Date(h.created_at).toLocaleDateString(locale, dateOptions)}</td>
    176                 <td data-title='Name'><Link to={generatePath('/heats/startlist/:heatId', {heatId:h.id})}>{h.name}</Link></td>
    177                 <td data-title='Location'>{h.location}</td>
    178                 <td data-title='Planned start'>{h.planned_start}</td>
    179                 <td data-title='Private' className='inline-block'><input type='checkbox' checked={h.private} onChange={e => changeVisibility(e, h.id, h.name, session)} /></td>
    180                 <td><button onClick={e => deleteHeat(e, h.id, h.name, session)}>&ndash; del</button></td>
    181               </tr>
    182             ))}
    183             <tr className='input'>
    184               <td className='right'><i>* required</i></td>
    185               <td data-title='Name *'>
    186                 <input type='text' name='name' />
    187               </td>
    188               <td data-title='Location'>
    189                 <input type='text' name='location' />
    190               </td>
    191               <td data-title='Planned start'>
    192                 <input
    193                   type='time'
    194                   name='planned_start' />
    195               </td>
    196               <td data-title='Private' className='inline-block'>
    197                 <input
    198                   type='checkbox'
    199                   name='private'
    200                   checked={privateHeatChecked}
    201                   onChange={(e) => setPrivateHeatChecked(e.target.checked)} />
    202               </td>
    203               <td>
    204                 <button type='submit'>&#43; new</button>
    205               </td>
    206             </tr>
    207           </tbody>
    208         </table>
    209       </form>
    210       <ExportForm heats={heats} />
    211     </div>
    212   )
    213 }
    214 
    215 function Heats({session}) {
    216     return (
    217       <div>
    218         {!session.auth ? <Auth /> : <HeatForm session={session} />}
    219       </div>
    220     )
    221   }
    222   
    223 export default Heats;