mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 21:56:50 -04:00
Add backup and coupon reports
This commit is contained in:
3
templates/backups.csv
Normal file
3
templates/backups.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
Rank,Called,TeamId,RankingPoints
|
||||
{{range $ranking := .}}{{$ranking.Rank}},{{$ranking.Called}},{{$ranking.TeamId}},{{$ranking.RankingPoints}}
|
||||
{{end}}
|
||||
|
@@ -56,6 +56,8 @@
|
||||
<li><a target="_blank" href="/reports/pdf/schedule/qualification">Qualification Schedule</a></li>
|
||||
<li><a target="_blank" href="/reports/pdf/schedule/elimination">Playoff Schedule</a></li>
|
||||
<li><a target="_blank" href="/reports/pdf/rankings">Standings</a></li>
|
||||
<li><a target="_blank" href="/reports/pdf/backups">Backups</a></li>
|
||||
<li><a target="_blank" href="/reports/pdf/coupons">Coupons</a></li>
|
||||
<li><a target="_blank" href="/reports/pdf/teams?showHasConnected=true">Team Connection Status</a></li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">CSV Data Export</li>
|
||||
@@ -64,6 +66,7 @@
|
||||
<li><a target="_blank" href="/reports/csv/schedule/qualification">Qualification Schedule</a></li>
|
||||
<li><a target="_blank" href="/reports/csv/schedule/elimination">Playoff Schedule</a></li>
|
||||
<li><a target="_blank" href="/reports/csv/rankings">Standings</a></li>
|
||||
<li><a target="_blank" href="/reports/csv/backups">Backups</a></li>
|
||||
{{if .EventSettings.NetworkSecurityEnabled}}
|
||||
<li><a target="_blank" href="/reports/csv/wpa_keys">WPA Keys</a></li>
|
||||
{{end}}
|
||||
|
||||
265
web/reports.go
265
web/reports.go
@@ -6,7 +6,9 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Team254/cheesy-arena-lite/game"
|
||||
"github.com/Team254/cheesy-arena-lite/tournament"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
@@ -88,6 +90,269 @@ func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// findBackupTeams takes the list of teams at the event and returns a slice of
|
||||
// teams with the teams that are already members of alliances removed. The
|
||||
// second returned value is the set of teams that were backups but have already
|
||||
// been called back to the field.
|
||||
//
|
||||
// At events that run 4 team alliances, this will show all of the 3rd picks and
|
||||
// remaining teams.
|
||||
func (web *Web) findBackupTeams(rankings game.Rankings) (game.Rankings, map[int]bool, error) {
|
||||
var pruned game.Rankings
|
||||
|
||||
picks, err := web.arena.Database.GetAllAlliances()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(picks) == 0 {
|
||||
return nil, nil, errors.New("backup reports are unavilable until alliances have been selected")
|
||||
}
|
||||
|
||||
pickedTeams := make(map[int]bool)
|
||||
pickedBackups := make(map[int]bool)
|
||||
|
||||
for _, alliance := range picks {
|
||||
for _, team := range alliance {
|
||||
// Teams in third in an alliance are backups at events that use 3 team alliances.
|
||||
if team.PickPosition == 3 {
|
||||
pickedBackups[team.TeamId] = true
|
||||
continue
|
||||
}
|
||||
pickedTeams[team.TeamId] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, team := range rankings {
|
||||
if !pickedTeams[team.TeamId] {
|
||||
pruned = append(pruned, team)
|
||||
}
|
||||
}
|
||||
|
||||
return pruned, pickedBackups, nil
|
||||
}
|
||||
|
||||
// Define a backupTeam type so that we can pass the additional "Called" field
|
||||
// to the CSV template parser.
|
||||
type backupTeam struct {
|
||||
Rank int
|
||||
Called bool
|
||||
TeamId int
|
||||
RankingPoints int
|
||||
}
|
||||
|
||||
// Generates a CSV-formatted report of the qualification rankings.
|
||||
func (web *Web) backupsCsvReportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rankings, err := web.arena.Database.GetAllRankings()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
rankings, pickedBackups, err := web.findBackupTeams(rankings)
|
||||
_ = pickedBackups
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Copy the list of teams that are eligble backups and annotate them with
|
||||
// whether or not they've been picked already.
|
||||
var backupTeams []backupTeam
|
||||
for _, r := range rankings {
|
||||
backupTeams = append(backupTeams, backupTeam{
|
||||
Rank: r.Rank,
|
||||
Called: pickedBackups[r.TeamId],
|
||||
TeamId: r.TeamId,
|
||||
RankingPoints: r.RankingPoints,
|
||||
})
|
||||
}
|
||||
|
||||
// Don't set the content type as "text/csv", as that will trigger an automatic download in the browser.
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
template, err := web.parseFiles("templates/backups.csv")
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
err = template.ExecuteTemplate(w, "backups.csv", backupTeams)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a PDF-formatted report of the backup teams.
|
||||
func (web *Web) backupsPdfReportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rankings, err := web.arena.Database.GetAllRankings()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
rankings, pickedBackups, err := web.findBackupTeams(rankings)
|
||||
_ = pickedBackups
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// The widths of the table columns in mm, stored here so that they can be referenced for each row.
|
||||
colWidths := map[string]float64{"Rank": 13, "Called": 22, "Team": 22, "RP": 23}
|
||||
rowHeight := 6.5
|
||||
|
||||
pdf := gofpdf.New("P", "mm", "Letter", "font")
|
||||
pdf.AddPage()
|
||||
|
||||
// Render table header row.
|
||||
pdf.SetFont("Arial", "B", 10)
|
||||
pdf.SetFillColor(220, 220, 220)
|
||||
pdf.CellFormat(195, rowHeight, "Backup Report - "+web.arena.EventSettings.Name, "", 1, "C", false, 0, "")
|
||||
pdf.CellFormat(colWidths["Rank"], rowHeight, "Rank", "1", 0, "C", true, 0, "")
|
||||
pdf.CellFormat(colWidths["Called"], rowHeight, "Called?", "1", 0, "C", true, 0, "")
|
||||
pdf.CellFormat(colWidths["Team"], rowHeight, "Team", "1", 0, "C", true, 0, "")
|
||||
pdf.CellFormat(colWidths["RP"], rowHeight, "RP", "1", 1, "C", true, 0, "")
|
||||
for _, ranking := range rankings {
|
||||
var picked string
|
||||
if pickedBackups[ranking.TeamId] {
|
||||
picked = "Y"
|
||||
}
|
||||
|
||||
pdf.SetFont("Arial", "B", 10)
|
||||
pdf.CellFormat(colWidths["Rank"], rowHeight, strconv.Itoa(ranking.Rank), "1", 0, "C", false, 0, "")
|
||||
pdf.CellFormat(colWidths["Called"], rowHeight, picked, "1", 0, "C", false, 0, "")
|
||||
pdf.SetFont("Arial", "", 10)
|
||||
pdf.CellFormat(colWidths["Team"], rowHeight, strconv.Itoa(ranking.TeamId), "1", 0, "C", false, 0, "")
|
||||
pdf.CellFormat(colWidths["RP"], rowHeight, strconv.Itoa(ranking.RankingPoints), "1", 1, "C", false, 0, "")
|
||||
}
|
||||
|
||||
// Write out the PDF file as the HTTP response.
|
||||
w.Header().Set("Content-Type", "application/pdf")
|
||||
err = pdf.Output(w)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Coupon constants used in laying out the elminations coupons.
|
||||
const (
|
||||
// All units in mm
|
||||
cHPad = 5
|
||||
cVPad = 5
|
||||
cWidth = 95
|
||||
cHeight = 60
|
||||
cSideMargin = 10
|
||||
cTopMargin = 10
|
||||
cImgWidth = 25
|
||||
cWOffset = 10
|
||||
)
|
||||
|
||||
func (web *Web) couponsReportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
pdf := gofpdf.New("P", "mm", "Letter", "font")
|
||||
pdf.SetLineWidth(1)
|
||||
|
||||
alliances, err := web.arena.Database.GetAllAlliances()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
if len(alliances) == 0 {
|
||||
handleWebErr(w, errors.New("coupon report is unavailable until alliances have been selected"))
|
||||
return
|
||||
}
|
||||
|
||||
eventName := web.arena.EventSettings.Name
|
||||
|
||||
for page := 0; page < len(alliances)/4; page++ {
|
||||
heightAcc := cTopMargin
|
||||
pdf.AddPage()
|
||||
for i := page * 4; i < page*4+4 && i < len(alliances); i++ {
|
||||
pdf.SetFillColor(220, 220, 220)
|
||||
|
||||
allianceCaptain := alliances[i][0].TeamId
|
||||
|
||||
pdf.RoundedRect(cSideMargin, float64(heightAcc), cWidth, cHeight, 4, "1234", "D")
|
||||
timeoutX := cSideMargin + (cWidth * 0.5)
|
||||
timeoutY := float64(heightAcc) + (cHeight * 0.5)
|
||||
drawTimeoutCoupon(pdf, eventName, timeoutX, timeoutY, allianceCaptain, i+1)
|
||||
|
||||
pdf.RoundedRect(cWidth+cHPad+cSideMargin, float64(heightAcc), cWidth, cHeight, 4, "1234", "D")
|
||||
backupX := cSideMargin + cWidth + cHPad + (cWidth * 0.5)
|
||||
backupY := float64(heightAcc) + (cHeight * 0.5)
|
||||
heightAcc += cHeight + cVPad
|
||||
drawBackupCoupon(pdf, eventName, backupX, backupY, allianceCaptain, i+1)
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the PDF file as the HTTP response.
|
||||
w.Header().Set("Content-Type", "application/pdf")
|
||||
err = pdf.Output(w)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func drawTimeoutCoupon(pdf gofpdf.Pdf, eventName string, x float64, y float64, teamId int, allianceNumber int) {
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
drawPdfLogo(pdf, x, y, cImgWidth)
|
||||
|
||||
pdf.SetFont("Arial", "B", 24)
|
||||
drawCenteredText(pdf, "Timeout Coupon", x, y+10)
|
||||
|
||||
pdf.SetFont("Arial", "", 14)
|
||||
drawCenteredText(pdf, fmt.Sprintf("Alliance %v Captain %v", allianceNumber, teamId), x, y+20)
|
||||
drawEventWatermark(pdf, x, y, eventName)
|
||||
}
|
||||
|
||||
func drawBackupCoupon(pdf gofpdf.Pdf, eventName string, x float64, y float64, teamId int, allianceNumber int) {
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
drawPdfLogo(pdf, x, y, cImgWidth)
|
||||
|
||||
pdf.SetFont("Arial", "B", 24)
|
||||
drawCenteredText(pdf, "Backup Coupon", x, y+10)
|
||||
|
||||
pdf.SetFont("Arial", "", 14)
|
||||
drawCenteredText(pdf, fmt.Sprintf("Alliance %v Captain %v", allianceNumber, teamId), x, y+20)
|
||||
drawEventWatermark(pdf, x, y, eventName)
|
||||
}
|
||||
|
||||
func drawEventWatermark(pdf gofpdf.Pdf, x float64, y float64, name string) {
|
||||
// Left mark
|
||||
pdf.TransformBegin()
|
||||
offset := pdf.GetStringWidth(name) / 2
|
||||
pdf.SetFont("Arial", "B", 11)
|
||||
leftX := x - (cWidth / 2)
|
||||
bottomY := y + (cHeight / 2)
|
||||
pdf.TransformRotate(90, leftX, bottomY)
|
||||
pdf.SetTextColor(200, 200, 200)
|
||||
pdf.Text(leftX-offset+(cHeight/2)+cWOffset, bottomY+5, name)
|
||||
pdf.TransformEnd()
|
||||
|
||||
// Right mark
|
||||
pdf.TransformBegin()
|
||||
offset = pdf.GetStringWidth(name) / 2
|
||||
pdf.SetFont("Arial", "B", 11)
|
||||
rightX := x + (cWidth / 2)
|
||||
topY := y - (cHeight / 2)
|
||||
pdf.TransformRotate(270, rightX, topY)
|
||||
pdf.Text(rightX-offset+(cHeight/2)-cWOffset, topY+5, name)
|
||||
pdf.TransformEnd()
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
}
|
||||
|
||||
func drawCenteredText(pdf gofpdf.Pdf, txt string, x float64, y float64) {
|
||||
width := pdf.GetStringWidth(txt)
|
||||
pdf.Text(x-(width/2), y, txt)
|
||||
}
|
||||
|
||||
func drawPdfLogo(pdf gofpdf.Pdf, x float64, y float64, width float64) {
|
||||
pdf.ImageOptions("static/img/game-logo.png", x-(width/2), y-25, width, 0, false, gofpdf.ImageOptions{ImageType: "PNG", ReadDpi: true}, 0, "")
|
||||
// Centering dot used for testing
|
||||
//pdf.Circle(x, y, 2, "F")
|
||||
}
|
||||
|
||||
// Generates a CSV-formatted report of the match schedule.
|
||||
func (web *Web) scheduleCsvReportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
@@ -148,6 +148,9 @@ func (web *Web) newHandler() http.Handler {
|
||||
router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditPostHandler).Methods("POST")
|
||||
router.HandleFunc("/reports/csv/rankings", web.rankingsCsvReportHandler).Methods("GET")
|
||||
router.HandleFunc("/reports/pdf/rankings", web.rankingsPdfReportHandler).Methods("GET")
|
||||
router.HandleFunc("/reports/csv/backups", web.backupsCsvReportHandler).Methods("GET")
|
||||
router.HandleFunc("/reports/pdf/backups", web.backupsPdfReportHandler).Methods("GET")
|
||||
router.HandleFunc("/reports/pdf/coupons", web.couponsReportHandler).Methods("GET")
|
||||
router.HandleFunc("/reports/csv/schedule/{type}", web.scheduleCsvReportHandler).Methods("GET")
|
||||
router.HandleFunc("/reports/pdf/schedule/{type}", web.schedulePdfReportHandler).Methods("GET")
|
||||
router.HandleFunc("/reports/csv/teams", web.teamsCsvReportHandler).Methods("GET")
|
||||
|
||||
Reference in New Issue
Block a user