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

utils.js (4589B)


      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           if (b.bestHeat - a.bestHeat !== 0) {
     92             // rank by best heat first
     93             return b.bestHeat - a.bestHeat
     94           }
     95           // rank by least worst heat for identical best heats
     96           return b.worstHeat - a.worstHeat
     97         case 'worst':
     98           // rank by worst heat
     99           return b.worstHeat - a.worstHeat
    100         case 'total':
    101           // rank by total sum across heats
    102           return b.sum - a.sum
    103         default:
    104           // rank by heat totals
    105           let aHeatTotal = a.heats.find(h => h.heatId === r.value)?.summary
    106           let bHeatTotal = b.heats.find(h => h.heatId === r.value)?.summary
    107           return bHeatTotal - aHeatTotal
    108       }
    109     }
    110   }
    111 }
    112 
    113 // Scores concat with "+" for leaderboard entry i and heat h
    114 export const getScores = function(i, h) {
    115   const scores = i.heats.find(heat => heat.heatId === h.value)?.scores?.map(s => s.score.toFixed(1)).join(" + ")
    116   return scores
    117 }
    118 // Returns formatted string with summed scores and summary for leaderboard
    119 // entry i and heat h
    120 export const formatScores = function(i, h) {
    121   const scores = getScores(i, h)
    122   if (scores) {
    123     // get individual scores of the heat and score sum
    124     return getScores(i, h) + " = " + i.heats.find(heat => heat.heatId === h.value)?.summary.toFixed(1)
    125   } else {
    126     return NaN.toString()
    127   }
    128 }
    129 
    130 // export CSV as blob
    131 const exportCSV = function(csv, fileSlug) {
    132   // create blob from csv
    133   const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
    134 
    135   // download blob
    136   const url = URL.createObjectURL(blob)
    137   const link = document.createElement('a')
    138   link.href = url
    139   link.download = fileSlug + "-" + new Date().toISOString() + ".csv"
    140   document.body.appendChild(link)
    141   link.click()
    142   document.body.removeChild(link)
    143   // let browser know not to keep the reference to the file
    144   URL.revokeObjectURL(url)
    145 }