myheats

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

utils.js (4526B)


      1 const api_uri = import.meta.env.VITE_API_URI
      2 const api_port = import.meta.env.VITE_API_PORT
      3 
      4 export const exportHeatsToCSV = async function(e, { heats }) {
      5   e.preventDefault()
      6 
      7   // csv header
      8   let csv = "created_at,name,location,planned_start\n"
      9 
     10   // append athlete data
     11   for (let i = 0; i < heats.length; i++) {
     12     csv += heats[i].created_at + "," + heats[i].name+ "," + heats[i].location+ ","
     13       + heats[i].planned_start+ "\n"
     14   }
     15 
     16   exportCSV(csv, "heats")
     17 }
     18 
     19 export const exportAthletesToCSV = async function(e, { athletes }) {
     20   e.preventDefault()
     21 
     22   // csv header
     23   let csv = "created_at,nr,firstname,lastname,birthday,school\n"
     24 
     25   // append athlete data
     26   for (let i = 0; i < athletes.length; i++) {
     27     csv += athletes[i].created_at + "," + athletes[i].nr + "," + athletes[i].firstname + ","
     28       + athletes[i].lastname + "," + athletes[i].birthday + "," + athletes[i].school + "\n"
     29   }
     30 
     31   exportCSV(csv, "athletes")
     32 }
     33 
     34 export const exportJudgesToCSV = async function(e, { judges }) {
     35   e.preventDefault()
     36 
     37   // csv header
     38   let csv = "created_at,email,firstname,lastname\n"
     39 
     40   // append judge data
     41   for (let i = 0; i < judges.length; i++) {
     42     csv += judges[i].created_at + "," + judges[i].email + ","
     43       + judges[i].firstname+ "," + judges[i].lastname + "\n"
     44   }
     45 
     46   exportCSV(csv, "judges")
     47 }
     48 
     49 export const exportLeaderboardToCSV = async function(e, leaderboard, heatSelection, rankingComp) {
     50   e.preventDefault()
     51 
     52   if (leaderboard.length === 0) {
     53     alert('Leaderboard is empty, nothing to export')
     54     return
     55   }
     56 
     57   // concatenate heat labels
     58   const heatNames = heatSelection.map(h => h.label)
     59 
     60   // rank leaderboard by selected comparator for each entry i in the board,
     61   // for every heat h
     62   const heatSummaries = leaderboard.sort(rankByHeat(rankingComp)).map(i =>
     63     heatSelection.map(h => formatScores(i, h))
     64   )
     65 
     66   // csv header
     67   let csv = "rank,start_nr,firstname,lastname,birthday,school,"
     68              + heatNames + "," + "best,worst,total\n"
     69 
     70   // append leaderboard score results
     71   for (let i = 0; i < leaderboard.length; i++) {
     72     csv += i+1 + "," + leaderboard[i].nr + "," + leaderboard[i].firstname + "," + leaderboard[i].lastname + ","
     73       + leaderboard[i].birthday + "," + leaderboard[i].school + ","
     74       + heatSummaries[i] + ","
     75       + leaderboard[i].bestHeat + "," + leaderboard[i].worstHeat + "," + leaderboard[i].sum + "\n"
     76   }
     77 
     78   exportCSV(csv, "scores")
     79 }
     80 
     81 // define the ranking logic
     82 export const rankByHeat = function(rankingComp) {
     83   return function(a, b) {
     84     // rank by chosen heat or ranking comparator
     85     for (const r of rankingComp) {
     86       switch(r.value) {
     87         case 'start':
     88           // rank by start number
     89           return a.nr - b.nr
     90         case 'best':
     91           // rank by best heat first
     92           return b.bestHeat - a.bestHeat
     93           // rank by least worst heat for identical best heats
     94           return b.worstHeat - a.worstHeat
     95         case 'worst':
     96           // rank by worst heat
     97           return b.worstHeat - a.worstHeat
     98         case 'total':
     99           // rank by total sum across heats
    100           return b.sum - a.sum
    101         default:
    102           // rank by heat totals
    103           let aHeatTotal = a.heats.find(h => h.heatId === r.value)?.summary
    104           let bHeatTotal = b.heats.find(h => h.heatId === r.value)?.summary
    105           return bHeatTotal - aHeatTotal
    106       }
    107     }
    108   }
    109 }
    110 
    111 // Scores concat with "+" for leaderboard entry i and heat h
    112 export const getScores = function(i, h) {
    113   const scores = i.heats.find(heat => heat.heatId === h.value)?.scores?.map(s => s.score.toFixed(1)).join(" + ")
    114   return scores
    115 }
    116 // Returns formatted string with summed scores and summary for leaderboard
    117 // entry i and heat h
    118 export const formatScores = function(i, h) {
    119   const scores = getScores(i, h)
    120   if (scores) {
    121     // get individual scores of the heat and score sum
    122     return getScores(i, h) + " = " + i.heats.find(heat => heat.heatId === h.value)?.summary.toFixed(1)
    123   } else {
    124     return NaN.toString()
    125   }
    126 }
    127 
    128 // export CSV as blob
    129 const exportCSV = function(csv, fileSlug) {
    130   // create blob from csv
    131   const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
    132 
    133   // download blob
    134   const url = URL.createObjectURL(blob)
    135   const link = document.createElement('a')
    136   link.href = url
    137   link.download = fileSlug + "-" + new Date().toISOString() + ".csv"
    138   document.body.appendChild(link)
    139   link.click()
    140   document.body.removeChild(link)
    141   // let browser know not to keep the reference to the file
    142   URL.revokeObjectURL(url)
    143 }