Added alliance selection page.

This commit is contained in:
Patrick Fairbank
2014-06-10 21:45:40 -07:00
parent 0c971861f8
commit cc92cee445
7 changed files with 692 additions and 16 deletions

View File

@@ -198,6 +198,8 @@ func SchedulePdfReportHandler(w http.ResponseWriter, r *http.Request) {
matchType = "Qualification"
} else if matchType == "practice" {
matchType = "Practice"
} else if matchType == "elimination" {
matchType = "Elimination"
}
// Render match info row.

250
setup_alliance_selection.go Normal file
View File

@@ -0,0 +1,250 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Web routes for conducting the alliance selection process.
package main
import (
"fmt"
"html/template"
"net/http"
"strconv"
"time"
)
type RankedTeam struct {
Rank int
TeamId int
Picked bool
}
// Global vars to hold the alliances that are in the process of being selected.
var cachedAlliances [][]*AllianceTeam
var cachedRankedTeams []*RankedTeam
// Shows the alliance selection page.
func AllianceSelectionGetHandler(w http.ResponseWriter, r *http.Request) {
renderAllianceSelection(w, r, "")
}
// Updates the cache with the latest input from the client.
func AllianceSelectionPostHandler(w http.ResponseWriter, r *http.Request) {
if !canModifyAllianceSelection() {
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
return
}
// Reset picked state for each team in preparation for reconstructing it.
for _, team := range cachedRankedTeams {
team.Picked = false
}
// Iterate through all selections and update the alliances.
for i, alliance := range cachedAlliances {
for j, spot := range alliance {
teamString := r.PostFormValue(fmt.Sprintf("selection%d_%d", i, j))
if teamString == "" {
spot.TeamId = 0
} else {
teamId, err := strconv.Atoi(teamString)
if err != nil {
renderAllianceSelection(w, r, fmt.Sprintf("Invalid team number value '%s'.", teamString))
return
}
found := false
for _, team := range cachedRankedTeams {
if team.TeamId == teamId {
if team.Picked {
renderAllianceSelection(w, r, fmt.Sprintf("Team %d is already part of an alliance.", teamId))
return
}
found = true
team.Picked = true
spot.TeamId = teamId
break
}
}
if !found {
renderAllianceSelection(w, r, fmt.Sprintf("Team %d is not present at this event.", teamId))
return
}
}
}
}
http.Redirect(w, r, "/setup/alliance_selection", 302)
}
// Sets up the empty alliances and populates the ranked team list.
func AllianceSelectionStartHandler(w http.ResponseWriter, r *http.Request) {
if len(cachedAlliances) != 0 {
renderAllianceSelection(w, r, "Can't start alliance selection when it is already in progress.")
return
}
if !canModifyAllianceSelection() {
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
return
}
// Create a blank alliance set matching the event configuration.
cachedAlliances = make([][]*AllianceTeam, eventSettings.NumElimAlliances)
teamsPerAlliance := 3
if eventSettings.SelectionRound3Order != "" {
teamsPerAlliance = 4
}
for i := 0; i < eventSettings.NumElimAlliances; i++ {
cachedAlliances[i] = make([]*AllianceTeam, teamsPerAlliance)
for j := 0; j < teamsPerAlliance; j++ {
cachedAlliances[i][j] = &AllianceTeam{AllianceId: i + 1, PickPosition: j}
}
}
// Populate the ranked list of teams.
rankings, err := db.GetAllRankings()
if err != nil {
handleWebErr(w, err)
return
}
cachedRankedTeams = make([]*RankedTeam, len(rankings))
for i, ranking := range rankings {
cachedRankedTeams[i] = &RankedTeam{i + 1, ranking.TeamId, false}
}
http.Redirect(w, r, "/setup/alliance_selection", 302)
}
// Resets the alliance selection process back to the starting point.
func AllianceSelectionResetHandler(w http.ResponseWriter, r *http.Request) {
if !canModifyAllianceSelection() {
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
return
}
cachedAlliances = [][]*AllianceTeam{}
cachedRankedTeams = []*RankedTeam{}
http.Redirect(w, r, "/setup/alliance_selection", 302)
}
// Saves the selected alliances to the database and generates the first round of elimination matches.
func AllianceSelectionFinalizeHandler(w http.ResponseWriter, r *http.Request) {
if !canModifyAllianceSelection() {
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
return
}
location, _ := time.LoadLocation("Local")
startTime, err := time.ParseInLocation("2006-01-02 03:04:05 PM", r.PostFormValue("startTime"), location)
if err != nil {
renderAllianceSelection(w, r, "Must specify a valid start time for the elimination rounds.")
return
}
matchSpacingSec, err := strconv.Atoi(r.PostFormValue("matchSpacingSec"))
if err != nil || matchSpacingSec <= 0 {
renderAllianceSelection(w, r, "Must specify a valid match spacing for the elimination rounds.")
return
}
// Check that all spots are filled.
for _, alliance := range cachedAlliances {
for _, team := range alliance {
if team.TeamId <= 0 {
renderAllianceSelection(w, r, "Can't finalize alliance selection until all spots have been filled.")
return
}
}
}
// Save alliances to the database.
for _, alliance := range cachedAlliances {
for _, team := range alliance {
err := db.CreateAllianceTeam(team)
if err != nil {
handleWebErr(w, err)
return
}
}
}
// Generate the first round of elimination matches.
_, err = db.UpdateEliminationSchedule(startTime, matchSpacingSec)
if err != nil {
handleWebErr(w, err)
return
}
http.Redirect(w, r, "/setup/alliance_selection", 302)
}
func renderAllianceSelection(w http.ResponseWriter, r *http.Request, errorMessage string) {
template, err := template.ParseFiles("templates/alliance_selection.html", "templates/base.html")
if err != nil {
handleWebErr(w, err)
return
}
nextRow, nextCol := determineNextCell()
data := struct {
*EventSettings
Alliances [][]*AllianceTeam
RankedTeams []*RankedTeam
NextRow int
NextCol int
ErrorMessage string
}{eventSettings, cachedAlliances, cachedRankedTeams, nextRow, nextCol, errorMessage}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
return
}
}
// Returns true if it is safe to change the alliance selection (i.e. no elimination matches exist yet).
func canModifyAllianceSelection() bool {
matches, err := db.GetMatchesByType("elimination")
if err != nil || len(matches) > 0 {
return false
}
return true
}
// Returns the row and column of the next alliance selection spot that should have keyboard autofocus.
func determineNextCell() (int, int) {
// Check the first two columns.
for i, alliance := range cachedAlliances {
if alliance[0].TeamId == 0 {
return i, 0
}
if alliance[1].TeamId == 0 {
return i, 1
}
}
// Check the third column.
if eventSettings.SelectionRound2Order == "F" {
for i, alliance := range cachedAlliances {
if alliance[2].TeamId == 0 {
return i, 2
}
}
} else {
for i := len(cachedAlliances) - 1; i >= 0; i-- {
if cachedAlliances[i][2].TeamId == 0 {
return i, 2
}
}
}
// Check the fourth column.
if eventSettings.SelectionRound3Order == "F" {
for i, alliance := range cachedAlliances {
if alliance[3].TeamId == 0 {
return i, 3
}
}
} else if eventSettings.SelectionRound3Order == "L" {
for i := len(cachedAlliances) - 1; i >= 0; i-- {
if cachedAlliances[i][3].TeamId == 0 {
return i, 3
}
}
}
return -1, -1
}

View File

@@ -0,0 +1,261 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package main
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestSetupAllianceSelection(t *testing.T) {
clearDb()
defer clearDb()
var err error
db, err = OpenDatabase(testDbPath)
assert.Nil(t, err)
defer db.Close()
cachedAlliances = [][]*AllianceTeam{}
cachedRankedTeams = []*RankedTeam{}
eventSettings, _ = db.GetEventSettings()
eventSettings.NumElimAlliances = 15
eventSettings.SelectionRound3Order = "L"
for i := 1; i <= 10; i++ {
db.CreateRanking(&Ranking{TeamId: 100 + i, Rank: i})
}
// Check that there are no alliance placeholders to start.
recorder := getHttpResponse("/setup/alliance_selection")
assert.Equal(t, 200, recorder.Code)
assert.NotContains(t, recorder.Body.String(), "Captain")
assert.NotContains(t, recorder.Body.String(), ">110<")
// Start the alliance selection.
recorder = postHttpResponse("/setup/alliance_selection/start", "")
assert.Equal(t, 302, recorder.Code)
if assert.Equal(t, 15, len(cachedAlliances)) {
assert.Equal(t, 4, len(cachedAlliances[0]))
}
recorder = getHttpResponse("/setup/alliance_selection")
assert.Contains(t, recorder.Body.String(), "Captain")
assert.Contains(t, recorder.Body.String(), ">110<")
// Reset the alliance selection.
recorder = postHttpResponse("/setup/alliance_selection/reset", "")
assert.Equal(t, 302, recorder.Code)
assert.NotContains(t, recorder.Body.String(), "Captain")
assert.NotContains(t, recorder.Body.String(), ">110<")
eventSettings.NumElimAlliances = 3
eventSettings.SelectionRound3Order = ""
recorder = postHttpResponse("/setup/alliance_selection/start", "")
assert.Equal(t, 302, recorder.Code)
if assert.Equal(t, 3, len(cachedAlliances)) {
assert.Equal(t, 3, len(cachedAlliances[0]))
}
// Update one team at a time.
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=110")
assert.Equal(t, 302, recorder.Code)
assert.Equal(t, 110, cachedAlliances[0][0].TeamId)
recorder = getHttpResponse("/setup/alliance_selection")
assert.Contains(t, recorder.Body.String(), "\"110\"")
assert.NotContains(t, recorder.Body.String(), ">110<")
// Update multiple teams at a time.
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection0_1=102&selection1_0=103")
assert.Equal(t, 302, recorder.Code)
assert.Equal(t, 101, cachedAlliances[0][0].TeamId)
assert.Equal(t, 102, cachedAlliances[0][1].TeamId)
assert.Equal(t, 103, cachedAlliances[1][0].TeamId)
recorder = getHttpResponse("/setup/alliance_selection")
assert.Contains(t, recorder.Body.String(), ">110<")
// Update remainder of teams.
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection0_1=102&"+
"selection0_2=103&selection1_0=104&selection1_1=105&selection1_2=106&selection2_0=107&selection2_1=108&"+
"selection2_2=109")
assert.Equal(t, 302, recorder.Code)
recorder = getHttpResponse("/setup/alliance_selection")
assert.Contains(t, recorder.Body.String(), ">110<")
// Finalize alliance selection.
recorder = postHttpResponse("/setup/alliance_selection/finalize",
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
assert.Equal(t, 302, recorder.Code)
alliances, err := db.GetAllAlliances()
assert.Nil(t, err)
if assert.Equal(t, 3, len(alliances)) {
assert.Equal(t, 101, alliances[0][0].TeamId)
assert.Equal(t, 105, alliances[1][1].TeamId)
assert.Equal(t, 109, alliances[2][2].TeamId)
}
matches, err := db.GetMatchesByType("elimination")
assert.Nil(t, err)
assert.Equal(t, 6, len(matches))
}
func TestSetupAllianceSelectionErrors(t *testing.T) {
clearDb()
defer clearDb()
var err error
db, err = OpenDatabase(testDbPath)
assert.Nil(t, err)
defer db.Close()
cachedAlliances = [][]*AllianceTeam{}
cachedRankedTeams = []*RankedTeam{}
eventSettings, _ = db.GetEventSettings()
eventSettings.NumElimAlliances = 2
for i := 1; i <= 6; i++ {
db.CreateRanking(&Ranking{TeamId: 100 + i, Rank: i})
}
// Start an alliance selection that is already underway.
recorder := postHttpResponse("/setup/alliance_selection/start", "")
assert.Equal(t, 302, recorder.Code)
recorder = postHttpResponse("/setup/alliance_selection/start", "")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already in progress")
// Select invalid teams.
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=asdf")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "Invalid team number")
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=100")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "not present at this event")
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection1_1=101")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already part of an alliance")
// Finalize early and without required parameters.
recorder = postHttpResponse("/setup/alliance_selection/finalize",
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "until all spots have been filled")
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection0_1=102&"+
"selection0_2=103&selection1_0=104&selection1_1=105&selection1_2=106")
assert.Equal(t, 302, recorder.Code)
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=asdf&matchSpacingSec=100")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "valid start time")
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=2014-01-01 01:00:00 PM")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "valid match spacing")
recorder = postHttpResponse("/setup/alliance_selection/finalize",
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
assert.Equal(t, 302, recorder.Code)
// Do other things after finalization.
recorder = postHttpResponse("/setup/alliance_selection/finalize",
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already been finalized")
recorder = postHttpResponse("/setup/alliance_selection/reset", "")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already been finalized")
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=asdf")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already been finalized")
cachedAlliances = [][]*AllianceTeam{}
cachedRankedTeams = []*RankedTeam{}
recorder = postHttpResponse("/setup/alliance_selection/start", "")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already been finalized")
}
func TestSetupAllianceSelectionAutofocus(t *testing.T) {
clearDb()
defer clearDb()
var err error
db, err = OpenDatabase(testDbPath)
assert.Nil(t, err)
defer db.Close()
cachedAlliances = [][]*AllianceTeam{}
cachedRankedTeams = []*RankedTeam{}
eventSettings, _ = db.GetEventSettings()
eventSettings.NumElimAlliances = 2
// Straight draft.
eventSettings.SelectionRound2Order = "F"
eventSettings.SelectionRound3Order = "F"
recorder := postHttpResponse("/setup/alliance_selection/start", "")
assert.Equal(t, 302, recorder.Code)
i, j := determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 0, j)
cachedAlliances[0][0].TeamId = 1
i, j = determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 1, j)
cachedAlliances[0][1].TeamId = 2
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 0, j)
cachedAlliances[1][0].TeamId = 3
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 1, j)
cachedAlliances[1][1].TeamId = 4
i, j = determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 2, j)
cachedAlliances[0][2].TeamId = 5
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 2, j)
cachedAlliances[1][2].TeamId = 6
i, j = determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 3, j)
cachedAlliances[0][3].TeamId = 7
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 3, j)
cachedAlliances[1][3].TeamId = 8
i, j = determineNextCell()
assert.Equal(t, -1, i)
assert.Equal(t, -1, j)
// Double-serpentine draft.
eventSettings.SelectionRound2Order = "L"
eventSettings.SelectionRound3Order = "L"
recorder = postHttpResponse("/setup/alliance_selection/reset", "")
assert.Equal(t, 302, recorder.Code)
recorder = postHttpResponse("/setup/alliance_selection/start", "")
assert.Equal(t, 302, recorder.Code)
i, j = determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 0, j)
cachedAlliances[0][0].TeamId = 1
i, j = determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 1, j)
cachedAlliances[0][1].TeamId = 2
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 0, j)
cachedAlliances[1][0].TeamId = 3
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 1, j)
cachedAlliances[1][1].TeamId = 4
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 2, j)
cachedAlliances[1][2].TeamId = 5
i, j = determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 2, j)
cachedAlliances[0][2].TeamId = 6
i, j = determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 3, j)
cachedAlliances[1][3].TeamId = 7
i, j = determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 3, j)
cachedAlliances[0][3].TeamId = 8
i, j = determineNextCell()
assert.Equal(t, -1, i)
assert.Equal(t, -1, j)
}

View File

@@ -28,7 +28,7 @@ func ScheduleGetHandler(w http.ResponseWriter, r *http.Request) {
cachedScheduleBlocks = append(cachedScheduleBlocks, ScheduleBlock{startTime, 10, 360})
cachedMatchType = "practice"
}
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches, "")
renderSchedule(w, r, "")
}
// Generates the schedule and presents it for review without saving it to the database.
@@ -36,8 +36,7 @@ func ScheduleGeneratePostHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
scheduleBlocks, err := getScheduleBlocks(r)
if err != nil {
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
"Incomplete or invalid schedule block parameters specified.")
renderSchedule(w, r, "Incomplete or invalid schedule block parameters specified.")
return
}
@@ -48,19 +47,18 @@ func ScheduleGeneratePostHandler(w http.ResponseWriter, r *http.Request) {
return
}
if len(teams) == 0 {
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
"No team list is configured. Set up the list of teams at the event before generating the schedule.")
renderSchedule(w, r, "No team list is configured. Set up the list of teams at the event before "+
"generating the schedule.")
return
}
if len(teams) < 18 {
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
fmt.Sprintf("There are only %d teams. There must be at least 18 teams to generate a schedule.", len(teams)))
renderSchedule(w, r, fmt.Sprintf("There are only %d teams. There must be at least 18 teams to generate "+
"a schedule.", len(teams)))
return
}
matches, err := BuildRandomSchedule(teams, scheduleBlocks, r.PostFormValue("matchType"))
if err != nil {
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
fmt.Sprintf("Error generating schedule: %s.", err.Error()))
renderSchedule(w, r, fmt.Sprintf("Error generating schedule: %s.", err.Error()))
return
}
@@ -96,9 +94,8 @@ func ScheduleSavePostHandler(w http.ResponseWriter, r *http.Request) {
return
}
if len(existingMatches) > 0 {
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
fmt.Sprintf("Can't save schedule because a schedule of %d %s matches already exists. Clear it first "+
" on the Settings page.", len(existingMatches), cachedMatchType))
renderSchedule(w, r, fmt.Sprintf("Can't save schedule because a schedule of %d %s matches already "+
"exists. Clear it first on the Settings page.", len(existingMatches), cachedMatchType))
return
}
@@ -112,8 +109,7 @@ func ScheduleSavePostHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/setup/schedule", 302)
}
func renderSchedule(w http.ResponseWriter, r *http.Request, matchType string, scheduleBlocks []ScheduleBlock,
matches []Match, teamFirstMatches map[int]string, errorMessage string) {
func renderSchedule(w http.ResponseWriter, r *http.Request, errorMessage string) {
teams, err := db.GetAllTeams()
if err != nil {
handleWebErr(w, err)
@@ -132,7 +128,8 @@ func renderSchedule(w http.ResponseWriter, r *http.Request, matchType string, sc
Matches []Match
TeamFirstMatches map[int]string
ErrorMessage string
}{eventSettings, matchType, scheduleBlocks, len(teams), matches, teamFirstMatches, errorMessage}
}{eventSettings, cachedMatchType, cachedScheduleBlocks, len(teams), cachedMatches, cachedTeamFirstMatches,
errorMessage}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)

View File

@@ -0,0 +1,161 @@
{{define "title"}}Alliance Selection{{end}}
{{define "body"}}
<div class="row">
{{if .ErrorMessage}}
<div class="alert alert-dismissable alert-danger">
<button type="button" class="close" data-dismiss="alert">×</button>
{{.ErrorMessage}}
</div>
{{end}}
{{if len .Alliances | eq 0}}
<div class="col-lg-3">
<form action="/setup/alliance_selection/start" method="POST">
<legend>Alliance Selection</legend>
<button type="submit" class="btn btn-info">Start Alliance Selection</button>
</form>
</div>
{{else}}
<form action="/setup/alliance_selection" method="POST">
<div class="col-lg-3 ">
<legend>Alliance Selection</legend>
<div class="form-group">
<button type="submit" class="btn btn-info">Update</button>
</div>
<div class="form-group">
<button type="button" class="btn btn-danger"
onclick="$('#confirmResetAllianceSelection').modal('show'); return false;">
Reset Alliance Selection
</button>
</div>
<div class="form-group">
<button type="button" class="btn btn-primary"
onclick="$('#confirmFinalizeAllianceSelection').modal('show'); return false;">
Finalize Alliance Selection
</button>
</div>
</div>
<div class="col-lg-5">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Alliance #</th>
<th>Captain</th>
<th>Pick 1</th>
<th>Pick 2</th>
{{if index .Alliances 0 | len | eq 4}}
<th>Pick 3</th>
{{end}}
</tr>
</thead>
<tbody>
{{range $i, $alliance := .Alliances}}
<tr>
<td class="col-lg-2">{{(index $alliance 0).AllianceId}}</td>
{{range $j, $team := $alliance}}
{{if eq $team.TeamId 0}}
<td class="col-lg-2">
<input type="text" class="form-control input-sm" name="selection{{$i}}_{{$j}}" value=""
{{if and (eq $i $.NextRow) (eq $j $.NextCol)}}autofocus{{end}}
oninput="$(this).parent().addClass('has-warning');" />
</td>
{{else}}
<td class="col-lg-2">
<input type="text" class="form-control input-sm" name="selection{{$i}}_{{$j}}"
value="{{$team.TeamId}}" oninput="$(this).parent().addClass('has-warning');" />
</td>
{{end}}
{{end}}
</tr>
{{end}}
</tbody>
</table>
Hint: Press 'Enter' after entering each team number for easiest use.
</div>
</form>
<div class="col-lg-2">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Rank</th>
<th>Team</th>
</tr>
</thead>
<tbody>
{{range $team := .RankedTeams}}
{{if not $team.Picked}}
<tr>
<td>{{$team.Rank}}</td>
<td>{{$team.TeamId}}</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
<div id="confirmResetAllianceSelection" class="modal" style="top: 20%;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Confirm</h4>
</div>
<div class="modal-body">
<p>Are you sure you want to reset the alliance selection process?</p>
</div>
<div class="modal-footer">
<form class="form-horizontal" action="/setup/alliance_selection/reset" method="POST">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger">Reset Alliance Selection</button>
</form>
</div>
</div>
</div>
</div>
<div id="confirmFinalizeAllianceSelection" class="modal" style="top: 20%;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Confirm</h4>
</div>
<form class="form-horizontal" action="/setup/alliance_selection/finalize" method="POST">
<div class="modal-body">
<p>Are you sure you want to finalize the alliance selection process?</p>
<div class="form-group">
<label class="col-lg-6 control-label">Elimination Round Start Time</label>
<div class="col-lg-6">
<div class="input-group date" id="startTimePicker" data-date-format="YYYY-MM-DD hh:mm:ss A">
<input type="text" class="form-control input-sm" name="startTime" />
<span class="input-group-addon input-sm">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-lg-6 control-label">Elimination Round Cycle Time (sec)</label>
<div class="col-lg-6">
<input type="text" class="form-control input-sm" name="matchSpacingSec" value="480" />
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Finalize Alliance Selection</button>
</div>
</form>
</div>
</div>
</div>
{{end}}
{{define "script"}}
<script>
$(function() {
$("#displayBackgroundColor").colorpicker();
var startTime = moment(new Date()).hour(13).minute(0).second(0);
$("#startTimePicker").datetimepicker().data("DateTimePicker").setDate(startTime);
});
</script>
{{end}}

View File

@@ -23,7 +23,7 @@
<li><a href="/setup/settings">Settings</a></li>
<li><a href="/setup/teams">Team List</a></li>
<li><a href="/setup/schedule">Match Scheduling</a></li>
<li><a href="#">Alliance Selection</a></li>
<li><a href="/setup/alliance_selection">Alliance Selection</a></li>
</ul>
</li>
<li class="dropdown">

5
web.go
View File

@@ -77,6 +77,11 @@ func newHandler() http.Handler {
router.HandleFunc("/setup/schedule", ScheduleGetHandler).Methods("GET")
router.HandleFunc("/setup/schedule/generate", ScheduleGeneratePostHandler).Methods("POST")
router.HandleFunc("/setup/schedule/save", ScheduleSavePostHandler).Methods("POST")
router.HandleFunc("/setup/alliance_selection", AllianceSelectionGetHandler).Methods("GET")
router.HandleFunc("/setup/alliance_selection", AllianceSelectionPostHandler).Methods("POST")
router.HandleFunc("/setup/alliance_selection/start", AllianceSelectionStartHandler).Methods("POST")
router.HandleFunc("/setup/alliance_selection/reset", AllianceSelectionResetHandler).Methods("POST")
router.HandleFunc("/setup/alliance_selection/finalize", AllianceSelectionFinalizeHandler).Methods("POST")
router.HandleFunc("/reports/csv/rankings", RankingsCsvReportHandler)
router.HandleFunc("/reports/pdf/rankings", RankingsPdfReportHandler)
router.HandleFunc("/reports/json/rankings", RankingsJSONReportHandler)