Files
cheesy-arena-lite/web/reports.go

710 lines
24 KiB
Go
Executable File

// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Web handlers for generating CSV and PDF reports.
package web
import (
"bytes"
"errors"
"fmt"
"github.com/Team254/cheesy-arena-lite/bracket"
"github.com/Team254/cheesy-arena-lite/game"
"github.com/Team254/cheesy-arena-lite/model"
"github.com/Team254/cheesy-arena-lite/tournament"
"github.com/gorilla/mux"
"github.com/jung-kurt/gofpdf"
"net/http"
"strconv"
)
// Generates a CSV-formatted report of the qualification rankings.
func (web *Web) rankingsCsvReportHandler(w http.ResponseWriter, r *http.Request) {
rankings, err := web.arena.Database.GetAllRankings()
if err != nil {
handleWebErr(w, err)
return
}
// 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/rankings.csv")
if err != nil {
handleWebErr(w, err)
return
}
err = template.ExecuteTemplate(w, "rankings.csv", rankings)
if err != nil {
handleWebErr(w, err)
return
}
}
// Generates a PDF-formatted report of the qualification rankings.
func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request) {
rankings, err := web.arena.Database.GetAllRankings()
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, "Team": 22, "RP": 23, "Auto": 23, "Endgame": 23, "Teleop": 23,
"W-L-T": 23, "DQ": 23, "Played": 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, "Team Standings - "+web.arena.EventSettings.Name, "", 1, "C", false, 0, "")
pdf.CellFormat(colWidths["Rank"], rowHeight, "Rank", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Team", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["RP"], rowHeight, "RP", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Auto"], rowHeight, "Auto", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Endgame"], rowHeight, "Endgame", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Teleop"], rowHeight, "Teleop", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["W-L-T"], rowHeight, "W-L-T", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Played"], rowHeight, "Played", "1", 1, "C", true, 0, "")
for _, ranking := range rankings {
// Render ranking info row.
pdf.SetFont("Arial", "B", 10)
pdf.CellFormat(colWidths["Rank"], rowHeight, strconv.Itoa(ranking.Rank), "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", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Auto"], rowHeight, strconv.Itoa(ranking.AutoPoints), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Endgame"], rowHeight, strconv.Itoa(ranking.EndgamePoints), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Teleop"], rowHeight, strconv.Itoa(ranking.TeleopPoints), "1", 0, "C", false, 0, "")
record := fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties)
pdf.CellFormat(colWidths["W-L-T"], rowHeight, record, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Played"], rowHeight, strconv.Itoa(ranking.Played), "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
}
}
// 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
alliances, err := web.arena.Database.GetAllAlliances()
if err != nil {
return nil, nil, err
}
if len(alliances) == 0 {
return nil, nil, errors.New("backup teams report is unavailable until alliances have been selected")
}
pickedTeams := make(map[int]bool)
pickedBackups := make(map[int]bool)
for _, alliance := range alliances {
for i, allianceTeamId := range alliance.TeamIds {
// Teams in third in an alliance are backups at events that use 3 team alliances.
if i == 3 {
pickedBackups[allianceTeamId] = true
continue
}
pickedTeams[allianceTeamId] = 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) backupTeamsCsvReportHandler(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)
if err != nil {
handleWebErr(w, err)
return
}
// Copy the list of teams that are eligible 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 Teams - "+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 playoff alliance coupons.
const (
// All units in mm
cHPad = 5
cVPad = 5
cWidth = 95
cHeight = 60
cSideMargin = 10
cTopMargin = 10
cImgWidth = 25
cWOffset = 5
)
func (web *Web) couponsPdfReportHandler(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("playoff alliance coupons report is unavailable until alliances have been selected"))
return
}
eventName := web.arena.EventSettings.Name
for page := 0; page < (len(alliances)+3)/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].TeamIds[0]
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) {
pdf.SetFont("Arial", "B", 11)
pdf.SetTextColor(200, 200, 200)
textWidth := pdf.GetStringWidth(name)
// Left mark
pdf.TransformBegin()
pdf.TransformRotate(90, x, y)
pdf.Text(x-textWidth/2, y-cWidth/2+cWOffset, name)
pdf.TransformEnd()
// Right mark
pdf.TransformBegin()
pdf.TransformRotate(270, x, y)
pdf.Text(x-textWidth/2, y-cWidth/2+cWOffset, name)
pdf.TransformEnd()
}
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, "")
}
// Generates a CSV-formatted report of the match schedule.
func (web *Web) scheduleCsvReportHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
matches, err := web.arena.Database.GetMatchesByType(vars["type"])
if err != nil {
handleWebErr(w, err)
return
}
// 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/schedule.csv")
if err != nil {
handleWebErr(w, err)
return
}
err = template.ExecuteTemplate(w, "schedule.csv", matches)
if err != nil {
handleWebErr(w, err)
return
}
}
// Generates a PDF-formatted report of the match schedule.
func (web *Web) schedulePdfReportHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
matches, err := web.arena.Database.GetMatchesByType(vars["type"])
if err != nil {
handleWebErr(w, err)
return
}
teams, err := web.arena.Database.GetAllTeams()
if err != nil {
handleWebErr(w, err)
return
}
matchesPerTeam := 0
if len(teams) > 0 {
matchesPerTeam = len(matches) * tournament.TeamsPerMatch / len(teams)
}
// The widths of the table columns in mm, stored here so that they can be referenced for each row.
colWidths := map[string]float64{"Time": 35, "Type": 25, "Match": 15, "Team": 20}
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, "Match Schedule - "+web.arena.EventSettings.Name, "", 1, "C", false, 0, "")
pdf.CellFormat(colWidths["Time"], rowHeight, "Time", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Type"], rowHeight, "Type", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Match"], rowHeight, "Match", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Red 1", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Red 2", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Red 3", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Blue 1", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Blue 2", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Blue 3", "1", 1, "C", true, 0, "")
pdf.SetFont("Arial", "", 10)
for _, match := range matches {
height := rowHeight
borderStr := "1"
alignStr := "CM"
surrogate := false
if match.Red1IsSurrogate || match.Red2IsSurrogate || match.Red3IsSurrogate ||
match.Blue1IsSurrogate || match.Blue2IsSurrogate || match.Blue3IsSurrogate {
// If the match contains surrogates, the row needs to be taller to fit some text beneath team numbers.
height = 5.0
borderStr = "LTR"
alignStr = "CB"
surrogate = true
}
// Capitalize match types.
matchType := match.CapitalizedType()
formatTeam := func(teamId int) string {
if teamId == 0 {
return ""
} else {
return strconv.Itoa(teamId)
}
}
// Render match info row.
pdf.CellFormat(colWidths["Time"], height, match.Time.Local().Format("Mon 1/02 03:04 PM"), borderStr, 0,
alignStr, false, 0, "")
pdf.CellFormat(colWidths["Type"], height, matchType, borderStr, 0, alignStr, false, 0, "")
pdf.CellFormat(colWidths["Match"], height, match.DisplayName, borderStr, 0, alignStr, false, 0, "")
pdf.CellFormat(colWidths["Team"], height, formatTeam(match.Red1), borderStr, 0, alignStr, false, 0, "")
pdf.CellFormat(colWidths["Team"], height, formatTeam(match.Red2), borderStr, 0, alignStr, false, 0, "")
pdf.CellFormat(colWidths["Team"], height, formatTeam(match.Red3), borderStr, 0, alignStr, false, 0, "")
pdf.CellFormat(colWidths["Team"], height, formatTeam(match.Blue1), borderStr, 0, alignStr, false, 0, "")
pdf.CellFormat(colWidths["Team"], height, formatTeam(match.Blue2), borderStr, 0, alignStr, false, 0, "")
pdf.CellFormat(colWidths["Team"], height, formatTeam(match.Blue3), borderStr, 1, alignStr, false, 0, "")
if surrogate {
// Render the text that indicates which teams are surrogates.
height := 4.0
pdf.SetFont("Arial", "", 8)
pdf.CellFormat(colWidths["Time"], height, "", "LBR", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Type"], height, "", "LBR", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Match"], height, "", "LBR", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Team"], height, surrogateText(match.Red1IsSurrogate), "LBR", 0, "CT", false, 0,
"")
pdf.CellFormat(colWidths["Team"], height, surrogateText(match.Red2IsSurrogate), "LBR", 0, "CT", false, 0,
"")
pdf.CellFormat(colWidths["Team"], height, surrogateText(match.Red3IsSurrogate), "LBR", 0, "CT", false, 0,
"")
pdf.CellFormat(colWidths["Team"], height, surrogateText(match.Blue1IsSurrogate), "LBR", 0, "CT", false, 0,
"")
pdf.CellFormat(colWidths["Team"], height, surrogateText(match.Blue2IsSurrogate), "LBR", 0, "CT", false, 0,
"")
pdf.CellFormat(colWidths["Team"], height, surrogateText(match.Blue3IsSurrogate), "LBR", 1, "CT", false, 0,
"")
pdf.SetFont("Arial", "", 10)
}
}
if vars["type"] != "elimination" {
// Render some summary info at the bottom.
pdf.CellFormat(195, 10, fmt.Sprintf("Matches Per Team: %d", matchesPerTeam), "", 1, "L", 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
}
}
// Generates a CSV-formatted report of the team list.
func (web *Web) teamsCsvReportHandler(w http.ResponseWriter, r *http.Request) {
teams, err := web.arena.Database.GetAllTeams()
if err != nil {
handleWebErr(w, err)
return
}
// 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/teams.csv")
if err != nil {
handleWebErr(w, err)
return
}
err = template.ExecuteTemplate(w, "teams.csv", teams)
if err != nil {
handleWebErr(w, err)
return
}
}
// Generates a PDF-formatted report of the team list.
func (web *Web) teamsPdfReportHandler(w http.ResponseWriter, r *http.Request) {
teams, err := web.arena.Database.GetAllTeams()
if err != nil {
handleWebErr(w, err)
return
}
showHasConnected := r.URL.Query().Get("showHasConnected") == "true"
// The widths of the table columns in mm, stored here so that they can be referenced for each row.
var colWidths map[string]float64
if showHasConnected {
colWidths = map[string]float64{"Id": 12, "Name": 70, "Location": 65, "RookieYear": 23, "HasConnected": 25}
} else {
colWidths = map[string]float64{"Id": 12, "Name": 80, "Location": 80, "RookieYear": 23}
}
rowHeight := 6.5
pdf := gofpdf.New("P", "mm", "Letter", "font")
pdf.AddPage()
pdf.SetFont("Arial", "B", 10)
pdf.SetFillColor(220, 220, 220)
// Render table header row.
pdf.CellFormat(195, rowHeight, "Team List - "+web.arena.EventSettings.Name, "", 1, "C", false, 0, "")
pdf.CellFormat(colWidths["Id"], rowHeight, "Team", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Name"], rowHeight, "Name", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Location"], rowHeight, "Location", "1", 0, "C", true, 0, "")
if showHasConnected {
pdf.CellFormat(colWidths["RookieYear"], rowHeight, "Rookie Year", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["HasConnected"], rowHeight, "Connected?", "1", 1, "C", true, 0, "")
} else {
pdf.CellFormat(colWidths["RookieYear"], rowHeight, "Rookie Year", "1", 1, "C", true, 0, "")
}
pdf.SetFont("Arial", "", 10)
for _, team := range teams {
// Render team info row.
pdf.CellFormat(colWidths["Id"], rowHeight, strconv.Itoa(team.Id), "1", 0, "L", false, 0, "")
pdf.CellFormat(colWidths["Name"], rowHeight, team.Nickname, "1", 0, "L", false, 0, "")
location := fmt.Sprintf("%s, %s, %s", team.City, team.StateProv, team.Country)
pdf.CellFormat(colWidths["Location"], rowHeight, location, "1", 0, "L", false, 0, "")
if showHasConnected {
pdf.CellFormat(colWidths["RookieYear"], rowHeight, strconv.Itoa(team.RookieYear), "1", 0, "L", false, 0, "")
var hasConnected string
if team.HasConnected {
hasConnected = "Yes"
}
pdf.CellFormat(colWidths["HasConnected"], rowHeight, hasConnected, "1", 1, "L", false, 0, "")
} else {
pdf.CellFormat(colWidths["RookieYear"], rowHeight, strconv.Itoa(team.RookieYear), "1", 1, "L", 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
}
}
// Generates a CSV-formatted report of the WPA keys, for import into the radio kiosk.
func (web *Web) wpaKeysCsvReportHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
teams, err := web.arena.Database.GetAllTeams()
if err != nil {
handleWebErr(w, err)
return
}
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", "attachment; filename=wpa_keys.csv")
for _, team := range teams {
_, err := w.Write([]byte(fmt.Sprintf("%d,%s\r\n", team.Id, team.WpaKey)))
if err != nil {
handleWebErr(w, err)
return
}
}
}
// Generates a PDF-formatted report of the playoff alliances and the teams contained within.
func (web *Web) alliancesPdfReportHandler(w http.ResponseWriter, r *http.Request) {
alliances, err := web.arena.Database.GetAllAlliances()
if err != nil {
handleWebErr(w, err)
return
}
// Traverse the bracket to register the furthest level that the alliance has achieved.
allianceStatuses := make(map[int]string)
if web.arena.PlayoffBracket.IsComplete() {
allianceStatuses[web.arena.PlayoffBracket.Winner()] = "Winner\n "
allianceStatuses[web.arena.PlayoffBracket.Finalist()] = "Finalist\n "
}
web.arena.PlayoffBracket.ReverseRoundOrderTraversal(func(matchup *bracket.Matchup) {
if matchup.IsComplete() {
if _, ok := allianceStatuses[matchup.Loser()]; !ok {
allianceStatuses[matchup.Loser()] = fmt.Sprintf("Eliminated in\n%s", matchup.LongDisplayName())
}
} else {
if matchup.RedAllianceId > 0 {
allianceStatuses[matchup.RedAllianceId] = fmt.Sprintf("Playing in\n%s", matchup.LongDisplayName())
}
if matchup.BlueAllianceId > 0 {
allianceStatuses[matchup.BlueAllianceId] = fmt.Sprintf("Playing in\n%s", matchup.LongDisplayName())
}
}
})
teams, err := web.arena.Database.GetAllTeams()
if err != nil {
handleWebErr(w, err)
return
}
teamsMap := make(map[int]model.Team, len(teams))
for _, team := range teams {
teamsMap[team.Id] = team
}
// The widths of the table columns in mm, stored here so that they can be referenced for each row.
colWidths := map[string]float64{"Alliance": 23, "Id": 12, "Name": 80, "Location": 80}
rowHeight := 6.5
pdf := gofpdf.New("P", "mm", "Letter", "font")
pdf.AddPage()
pdf.SetFont("Arial", "B", 10)
pdf.SetFillColor(220, 220, 220)
// Render table header row.
pdf.CellFormat(195, rowHeight, "Playoff Alliances - "+web.arena.EventSettings.Name, "", 1, "C", false, 0, "")
pdf.CellFormat(colWidths["Alliance"], rowHeight, "Alliance", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Id"], rowHeight, "Team", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Name"], rowHeight, "Name", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Location"], rowHeight, "Location", "1", 1, "C", true, 0, "")
pdf.SetFont("Arial", "", 10)
xStart := pdf.GetX()
for _, alliance := range alliances {
yStart := pdf.GetY()
pdf.MultiCell(
colWidths["Alliance"],
rowHeight*float64(len(alliance.TeamIds))/5,
fmt.Sprintf(" \n%d\n%s\n ", alliance.Id, allianceStatuses[alliance.Id]),
"1",
"C",
false,
)
pdf.SetY(yStart)
for _, teamId := range alliance.TeamIds {
pdf.SetX(xStart + colWidths["Alliance"])
team := teamsMap[teamId]
pdf.CellFormat(colWidths["Id"], rowHeight, strconv.Itoa(team.Id), "1", 0, "L", false, 0, "")
pdf.CellFormat(colWidths["Name"], rowHeight, team.Nickname, "1", 0, "L", false, 0, "")
location := fmt.Sprintf("%s, %s, %s", team.City, team.StateProv, team.Country)
pdf.CellFormat(colWidths["Location"], rowHeight, location, "1", 1, "L", 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
}
}
// Generates a PDF-formatted report of the playoff bracket, relying on the browser to convert SVG to PDF (since no
// suitable Go library for doing so appears to exist).
func (web *Web) bracketPdfReportHandler(w http.ResponseWriter, r *http.Request) {
buffer := new(bytes.Buffer)
err := web.generateBracketSvg(buffer, nil, false)
if err != nil {
handleWebErr(w, err)
return
}
template, err := web.parseFiles("templates/bracket_report.html")
if err != nil {
handleWebErr(w, err)
return
}
err = template.ExecuteTemplate(w, "bracket_report.html", buffer.String())
if err != nil {
handleWebErr(w, err)
return
}
}
// Returns the text to display if a team is a surrogate.
func surrogateText(isSurrogate bool) string {
if isSurrogate {
return "(surrogate)"
} else {
return ""
}
}