diff --git a/reports.go b/reports.go index 5aac91b..bf1159a 100644 --- a/reports.go +++ b/reports.go @@ -14,6 +14,89 @@ import ( "text/template" ) +// Generates a CSV-formatted report of the qualification rankings. +func RankingsCsvReportHandler(w http.ResponseWriter, r *http.Request) { + rankings, err := db.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 := template.ParseFiles("templates/rankings.csv") + if err != nil { + handleWebErr(w, err) + return + } + err = template.Execute(w, rankings) + if err != nil { + handleWebErr(w, err) + return + } +} + +// Generates a PDF-formatted report of the qualification rankings. +func RankingsPdfReportHandler(w http.ResponseWriter, r *http.Request) { + rankings, err := db.GetAllRankings() + if err != nil { + handleWebErr(w, err) + return + } + eventSettings, err := db.GetEventSettings() + 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": 23, "QS": 20, "Assist": 20, "Auto": 20, + "T&C": 20, "G&F": 20, "Record": 20, "DQ": 20, "Played": 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, "Team Standings - "+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["QS"], rowHeight, "QS", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["Assist"], rowHeight, "Assist", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["Auto"], rowHeight, "Auto", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["T&C"], rowHeight, "T&C", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["G&F"], rowHeight, "G&F", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["Record"], rowHeight, "Record", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["DQ"], rowHeight, "DQ", "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["QS"], rowHeight, strconv.Itoa(ranking.QualificationScore), "1", 0, "C", false, 0, "") + pdf.CellFormat(colWidths["Assist"], rowHeight, strconv.Itoa(ranking.AssistPoints), "1", 0, "C", false, 0, "") + pdf.CellFormat(colWidths["Auto"], rowHeight, strconv.Itoa(ranking.AutoPoints), "1", 0, "C", false, 0, "") + pdf.CellFormat(colWidths["T&C"], rowHeight, strconv.Itoa(ranking.TrussCatchPoints), "1", 0, "C", false, 0, "") + pdf.CellFormat(colWidths["G&F"], rowHeight, strconv.Itoa(ranking.GoalFoulPoints), "1", 0, "C", false, 0, "") + record := fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties) + pdf.CellFormat(colWidths["Record"], rowHeight, record, "1", 0, "C", false, 0, "") + pdf.CellFormat(colWidths["DQ"], rowHeight, strconv.Itoa(ranking.Disqualifications), "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 + } +} + // Generates a CSV-formatted report of the match schedule. func ScheduleCsvReportHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/reports_test.go b/reports_test.go index cc2def7..5fcff26 100644 --- a/reports_test.go +++ b/reports_test.go @@ -11,6 +11,39 @@ import ( "time" ) +func TestRankingsCsvReport(t *testing.T) { + clearDb() + defer clearDb() + db, _ = OpenDatabase(testDbPath) + ranking1 := Ranking{1114, 2, 18, 1100, 625, 90, 554, 0.254, 9, 1, 0, 0, 10} + ranking2 := Ranking{254, 1, 20, 1100, 625, 90, 554, 0.254, 10, 0, 0, 0, 10} + db.CreateRanking(&ranking1) + db.CreateRanking(&ranking2) + + recorder := getHttpResponse("/reports/csv/rankings") + assert.Equal(t, 200, recorder.Code) + assert.Equal(t, "text/plain", recorder.HeaderMap["Content-Type"][0]) + expectedBody := "Rank,TeamId,QualificationScore,AssistPoints,AutoPoints,TrussCatchPoints,GoalFoulPoints," + + "Wins,Losses,Ties,Disqualifications,Played\n1,254,20,1100,625,90,554,10,0,0,0,10\n2,1114,18,1100,625," + + "90,554,9,1,0,0,10\n\n" + assert.Equal(t, expectedBody, recorder.Body.String()) +} + +func TestRankingsPdfReport(t *testing.T) { + clearDb() + defer clearDb() + db, _ = OpenDatabase(testDbPath) + ranking1 := Ranking{1114, 2, 18, 1100, 625, 90, 554, 0.254, 9, 1, 0, 0, 10} + ranking2 := Ranking{254, 1, 20, 1100, 625, 90, 554, 0.254, 10, 0, 0, 0, 10} + db.CreateRanking(&ranking1) + db.CreateRanking(&ranking2) + + // Can't really parse the PDF content and check it, so just check that what's sent back is a PDF. + recorder := getHttpResponse("/reports/pdf/rankings") + assert.Equal(t, 200, recorder.Code) + assert.Equal(t, "application/pdf", recorder.HeaderMap["Content-Type"][0]) +} + func TestScheduleCsvReport(t *testing.T) { clearDb() defer clearDb() diff --git a/templates/rankings.csv b/templates/rankings.csv new file mode 100644 index 0000000..2691cf7 --- /dev/null +++ b/templates/rankings.csv @@ -0,0 +1,3 @@ +Rank,TeamId,QualificationScore,AssistPoints,AutoPoints,TrussCatchPoints,GoalFoulPoints,Wins,Losses,Ties,Disqualifications,Played +{{range $ranking := .}}{{$ranking.Rank}},{{$ranking.TeamId}},{{$ranking.QualificationScore}},{{$ranking.AssistPoints}},{{$ranking.AutoPoints}},{{$ranking.TrussCatchPoints}},{{$ranking.GoalFoulPoints}},{{$ranking.Wins}},{{$ranking.Losses}},{{$ranking.Ties}},{{$ranking.Disqualifications}},{{$ranking.Played}} +{{end}} diff --git a/web.go b/web.go index 0ca2d3e..e7c8c41 100644 --- a/web.go +++ b/web.go @@ -22,6 +22,8 @@ func ServeWebInterface() { func newHandler() http.Handler { router := mux.NewRouter() + router.HandleFunc("/reports/csv/rankings", RankingsCsvReportHandler) + router.HandleFunc("/reports/pdf/rankings", RankingsPdfReportHandler) router.HandleFunc("/reports/csv/schedule/{type}", ScheduleCsvReportHandler) router.HandleFunc("/reports/pdf/schedule/{type}", SchedulePdfReportHandler) router.HandleFunc("/reports/csv/teams", TeamsCsvReportHandler)