diff --git a/db/db.go b/db/db.go index e7556ea..defd07a 100644 --- a/db/db.go +++ b/db/db.go @@ -2,6 +2,7 @@ package db import ( "database/sql" + "errors" "fmt" "log/slog" "os" @@ -11,6 +12,9 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// ErrNotFound is returned when a requested record is not found +var ErrNotFound = errors.New("record not found") + // DB represents the database connection type DB struct { *sql.DB @@ -134,6 +138,46 @@ func (db *DB) initSchema() error { return fmt.Errorf("failed to create heats table: %w", err) } + // Create settings table + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + `) + if err != nil { + return fmt.Errorf("failed to create settings table: %w", err) + } + + // Create heat_results table + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS heat_results ( + group_id INTEGER NOT NULL, + heat_number INTEGER NOT NULL, + lane1_time REAL NOT NULL, + lane1_position INTEGER NOT NULL, + lane2_time REAL NOT NULL, + lane2_position INTEGER NOT NULL, + lane3_time REAL NOT NULL, + lane3_position INTEGER NOT NULL, + lane4_time REAL NOT NULL, + lane4_position INTEGER NOT NULL, + PRIMARY KEY (group_id, heat_number), + FOREIGN KEY (group_id) REFERENCES groups (id) ON DELETE CASCADE + ); + `) + if err != nil { + return fmt.Errorf("failed to create heat_results table: %w", err) + } + + // Add default settings if they don't exist + _, err = db.Exec(` + INSERT OR IGNORE INTO settings (key, value) VALUES ('current_racing_group', '1'); + `) + if err != nil { + return fmt.Errorf("failed to add default settings: %w", err) + } + return nil } @@ -220,3 +264,180 @@ func (db *DB) DeleteHeats(groupID int64) error { _, err := db.Exec("DELETE FROM heats WHERE group_id = ?", groupID) return err } + +// GetCurrentRacingGroup gets the currently racing group +func (db *DB) GetCurrentRacingGroup() (models.Group, error) { + var group models.Group + + // Get the current racing group ID from settings + var groupID int64 + err := db.QueryRow("SELECT value FROM settings WHERE key = 'current_racing_group'").Scan(&groupID) + if err != nil { + if err == sql.ErrNoRows { + // If no group is set, get the first group + err = db.QueryRow("SELECT id, name, description FROM groups ORDER BY id LIMIT 1").Scan( + &group.ID, &group.Name, &group.Description) + if err != nil { + return group, err + } + + // Set this as the current racing group + _, err = db.Exec("INSERT OR REPLACE INTO settings (key, value) VALUES ('current_racing_group', ?)", group.ID) + return group, err + } + return group, err + } + + // Get the group details + err = db.QueryRow("SELECT id, name, description FROM groups WHERE id = ?", groupID).Scan( + &group.ID, &group.Name, &group.Description) + return group, err +} + +// SetCurrentRacingGroup sets the currently racing group +func (db *DB) SetCurrentRacingGroup(groupID int64) error { + _, err := db.Exec("INSERT OR REPLACE INTO settings (key, value) VALUES ('current_racing_group', ?)", groupID) + return err +} + +// GetCurrentHeatNumber gets the current heat number for a group +func (db *DB) GetCurrentHeatNumber(groupID int64) (int, error) { + var heatNum int + key := fmt.Sprintf("current_heat_%d", groupID) + + err := db.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&heatNum) + if err != nil { + if err == sql.ErrNoRows { + // If no heat is set, default to 1 + _, err = db.Exec("INSERT INTO settings (key, value) VALUES (?, 1)", key) + return 1, err + } + return 0, err + } + + return heatNum, nil +} + +// SetCurrentHeatNumber sets the current heat number for a group +func (db *DB) SetCurrentHeatNumber(groupID int64, heatNum int) error { + key := fmt.Sprintf("current_heat_%d", groupID) + _, err := db.Exec("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", key, heatNum) + return err +} + +// SaveHeatResult saves the result of a heat +func (db *DB) SaveHeatResult(result models.HeatResult) error { + // Check if result already exists + var count int + err := db.QueryRow("SELECT COUNT(*) FROM heat_results WHERE group_id = ? AND heat_number = ?", + result.GroupID, result.HeatNumber).Scan(&count) + if err != nil { + return err + } + + if count > 0 { + // Update existing result + _, err = db.Exec(` + UPDATE heat_results SET + lane1_time = ?, lane1_position = ?, + lane2_time = ?, lane2_position = ?, + lane3_time = ?, lane3_position = ?, + lane4_time = ?, lane4_position = ? + WHERE group_id = ? AND heat_number = ?`, + result.Lane1Time, result.Lane1Position, + result.Lane2Time, result.Lane2Position, + result.Lane3Time, result.Lane3Position, + result.Lane4Time, result.Lane4Position, + result.GroupID, result.HeatNumber) + } else { + // Insert new result + _, err = db.Exec(` + INSERT INTO heat_results ( + group_id, heat_number, + lane1_time, lane1_position, + lane2_time, lane2_position, + lane3_time, lane3_position, + lane4_time, lane4_position + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + result.GroupID, result.HeatNumber, + result.Lane1Time, result.Lane1Position, + result.Lane2Time, result.Lane2Position, + result.Lane3Time, result.Lane3Position, + result.Lane4Time, result.Lane4Position) + } + + return err +} + +// GetHeatResult gets the result of a specific heat +func (db *DB) GetHeatResult(groupID int64, heatNum int) (*models.HeatResult, error) { + result := &models.HeatResult{} + + err := db.QueryRow(` + SELECT + group_id, heat_number, + lane1_time, lane1_position, + lane2_time, lane2_position, + lane3_time, lane3_position, + lane4_time, lane4_position + FROM heat_results + WHERE group_id = ? AND heat_number = ?`, + groupID, heatNum).Scan( + &result.GroupID, &result.HeatNumber, + &result.Lane1Time, &result.Lane1Position, + &result.Lane2Time, &result.Lane2Position, + &result.Lane3Time, &result.Lane3Position, + &result.Lane4Time, &result.Lane4Position) + + if err != nil { + if err == sql.ErrNoRows { + return nil, ErrNotFound + } + return nil, err + } + + return result, nil +} + +// GetHeatResults gets all heat results for a group +func (db *DB) GetHeatResults(groupID int64) ([]models.HeatResult, error) { + rows, err := db.Query(` + SELECT + group_id, heat_number, + lane1_time, lane1_position, + lane2_time, lane2_position, + lane3_time, lane3_position, + lane4_time, lane4_position + FROM heat_results + WHERE group_id = ? + ORDER BY heat_number`, + groupID) + if err != nil { + return nil, err + } + defer rows.Close() + + var results []models.HeatResult + for rows.Next() { + var result models.HeatResult + err := rows.Scan( + &result.GroupID, &result.HeatNumber, + &result.Lane1Time, &result.Lane1Position, + &result.Lane2Time, &result.Lane2Position, + &result.Lane3Time, &result.Lane3Position, + &result.Lane4Time, &result.Lane4Position) + if err != nil { + return nil, err + } + results = append(results, result) + } + + return results, nil +} + +// DeleteHeatResult deletes a heat result +func (db *DB) DeleteHeatResult(groupID int64, heatNum int) error { + _, err := db.Exec("DELETE FROM heat_results WHERE group_id = ? AND heat_number = ?", + groupID, heatNum) + return err +} diff --git a/models/models.go b/models/models.go index ef5386f..c003c65 100644 --- a/models/models.go +++ b/models/models.go @@ -53,3 +53,17 @@ type Heat struct { Lane3ID *int64 `json:"lane3_id"` Lane4ID *int64 `json:"lane4_id"` } + +// HeatResult represents the result of a heat +type HeatResult struct { + GroupID int64 `json:"group_id"` + HeatNumber int `json:"heat_number"` + Lane1Time float64 `json:"lane1_time"` + Lane1Position int `json:"lane1_position"` + Lane2Time float64 `json:"lane2_time"` + Lane2Position int `json:"lane2_position"` + Lane3Time float64 `json:"lane3_time"` + Lane3Position int `json:"lane3_position"` + Lane4Time float64 `json:"lane4_time"` + Lane4Position int `json:"lane4_position"` +} diff --git a/web/server.go b/web/server.go index 02b5ed7..3d609f8 100644 --- a/web/server.go +++ b/web/server.go @@ -153,6 +153,20 @@ func (s *Server) routes() { // Add validate car number route s.router.Get("/api/validate/car-number", s.handleValidateCarNumber()) + + // Add race manager routes + s.router.Get("/race", s.handleRacePublic()) + s.router.Get("/race/manage", s.handleRaceManage()) + + // Add race API routes + s.router.Route("/api/race", func(r chi.Router) { + r.Get("/current-heat", s.handleCurrentHeat()) + r.Post("/next-heat", s.handleNextHeat()) + r.Post("/previous-heat", s.handlePreviousHeat()) + r.Post("/save-result", s.handleSaveHeatResult()) + r.Post("/rerun-heat", s.handleRerunHeat()) + r.Post("/set-group", s.handleSetRacingGroup()) + }) } // Start starts the web server @@ -991,3 +1005,503 @@ func (s *Server) handleValidateCarNumber() http.HandlerFunc { } } } + +// handleRacePublic renders the public race view page +func (s *Server) handleRacePublic() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Get the currently racing group + currentGroup, err := s.db.GetCurrentRacingGroup() + if err != nil { + s.logger.Error("Failed to get current racing group", "error", err) + http.Error(w, "Failed to get current racing group", http.StatusInternalServerError) + return + } + + // Get current heat number + currentHeatNum, err := s.db.GetCurrentHeatNumber(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get current heat number", "error", err) + http.Error(w, "Failed to get current heat number", http.StatusInternalServerError) + return + } + + // Get heats for the group + heats, err := s.db.GetHeats(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get heats", "error", err) + http.Error(w, "Failed to get heats", http.StatusInternalServerError) + return + } + + // Get racers for the group + racers, err := s.db.GetRacersByGroup(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get racers", "error", err) + http.Error(w, "Failed to get racers", http.StatusInternalServerError) + return + } + + // Get heat results + results, err := s.db.GetHeatResults(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get heat results", "error", err) + http.Error(w, "Failed to get heat results", http.StatusInternalServerError) + return + } + + // Render template + component := templates.RacePublic(currentGroup, heats, racers, currentHeatNum, results) + if err := component.Render(r.Context(), w); err != nil { + s.logger.Error("Failed to render race public template", "error", err) + http.Error(w, "Failed to render page", http.StatusInternalServerError) + } + } +} + +// handleRaceManage renders the race management page +func (s *Server) handleRaceManage() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Get all groups + groups, err := s.db.GetGroups() + if err != nil { + s.logger.Error("Failed to get groups", "error", err) + http.Error(w, "Failed to get groups", http.StatusInternalServerError) + return + } + + // Get the currently racing group + currentGroup, err := s.db.GetCurrentRacingGroup() + if err != nil { + s.logger.Error("Failed to get current racing group", "error", err) + http.Error(w, "Failed to get current racing group", http.StatusInternalServerError) + return + } + + // Get current heat number + currentHeatNum, err := s.db.GetCurrentHeatNumber(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get current heat number", "error", err) + http.Error(w, "Failed to get current heat number", http.StatusInternalServerError) + return + } + + // Get heats for the group + heats, err := s.db.GetHeats(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get heats", "error", err) + http.Error(w, "Failed to get heats", http.StatusInternalServerError) + return + } + + // Get racers for the group + racers, err := s.db.GetRacersByGroup(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get racers", "error", err) + http.Error(w, "Failed to get racers", http.StatusInternalServerError) + return + } + + // Get heat results + results, err := s.db.GetHeatResults(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get heat results", "error", err) + http.Error(w, "Failed to get heat results", http.StatusInternalServerError) + return + } + + // Render template + component := templates.RaceManage(groups, currentGroup, heats, racers, currentHeatNum, results) + if err := component.Render(r.Context(), w); err != nil { + s.logger.Error("Failed to render race manage template", "error", err) + http.Error(w, "Failed to render page", http.StatusInternalServerError) + } + } +} + +// API handlers for race management + +// handleCurrentHeat returns the current heat data +func (s *Server) handleCurrentHeat() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Get the currently racing group + currentGroup, err := s.db.GetCurrentRacingGroup() + if err != nil { + s.logger.Error("Failed to get current racing group", "error", err) + http.Error(w, "Failed to get current racing group", http.StatusInternalServerError) + return + } + + // Get current heat number + currentHeatNum, err := s.db.GetCurrentHeatNumber(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get current heat number", "error", err) + http.Error(w, "Failed to get current heat number", http.StatusInternalServerError) + return + } + + // Get heats for the group + heats, err := s.db.GetHeats(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get heats", "error", err) + http.Error(w, "Failed to get heats", http.StatusInternalServerError) + return + } + + // Get racers for the group + racers, err := s.db.GetRacersByGroup(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get racers", "error", err) + http.Error(w, "Failed to get racers", http.StatusInternalServerError) + return + } + + // Find current heat + var currentHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == currentHeatNum { + currentHeat = heat + break + } + } + + // Create response with racer details + type LaneInfo struct { + Lane int `json:"lane"` + RacerID int64 `json:"racer_id"` + Name string `json:"name"` + CarNum string `json:"car_number"` + Time interface{} `json:"time"` + Position interface{} `json:"position"` + } + + response := struct { + HeatNumber int `json:"heat_number"` + TotalHeats int `json:"total_heats"` + GroupID int64 `json:"group_id"` + GroupName string `json:"group_name"` + Lanes []LaneInfo `json:"lanes"` + }{ + HeatNumber: currentHeatNum, + TotalHeats: len(heats), + GroupID: currentGroup.ID, + GroupName: currentGroup.Name, + Lanes: make([]LaneInfo, 0), + } + + // Get heat result if available + result, err := s.db.GetHeatResult(currentGroup.ID, currentHeatNum) + if err != nil && err != db.ErrNotFound { + s.logger.Error("Failed to get heat result", "error", err) + } + + // Add lane 1 info + if currentHeat.Lane1ID != nil { + lane := LaneInfo{Lane: 1, RacerID: *currentHeat.Lane1ID} + for _, racer := range racers { + if racer.ID == *currentHeat.Lane1ID { + lane.Name = racer.FirstName + " " + racer.LastName + lane.CarNum = racer.CarNumber + break + } + } + if result != nil { + lane.Time = result.Lane1Time + lane.Position = result.Lane1Position + } + response.Lanes = append(response.Lanes, lane) + } + + // Add lane 2 info + if currentHeat.Lane2ID != nil { + lane := LaneInfo{Lane: 2, RacerID: *currentHeat.Lane2ID} + for _, racer := range racers { + if racer.ID == *currentHeat.Lane2ID { + lane.Name = racer.FirstName + " " + racer.LastName + lane.CarNum = racer.CarNumber + break + } + } + if result != nil { + lane.Time = result.Lane2Time + lane.Position = result.Lane2Position + } + response.Lanes = append(response.Lanes, lane) + } + + // Add lane 3 info + if currentHeat.Lane3ID != nil { + lane := LaneInfo{Lane: 3, RacerID: *currentHeat.Lane3ID} + for _, racer := range racers { + if racer.ID == *currentHeat.Lane3ID { + lane.Name = racer.FirstName + " " + racer.LastName + lane.CarNum = racer.CarNumber + break + } + } + if result != nil { + lane.Time = result.Lane3Time + lane.Position = result.Lane3Position + } + response.Lanes = append(response.Lanes, lane) + } + + // Add lane 4 info + if currentHeat.Lane4ID != nil { + lane := LaneInfo{Lane: 4, RacerID: *currentHeat.Lane4ID} + for _, racer := range racers { + if racer.ID == *currentHeat.Lane4ID { + lane.Name = racer.FirstName + " " + racer.LastName + lane.CarNum = racer.CarNumber + break + } + } + if result != nil { + lane.Time = result.Lane4Time + lane.Position = result.Lane4Position + } + response.Lanes = append(response.Lanes, lane) + } + + // Return JSON response + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + } +} + +// handleNextHeat advances to the next heat +func (s *Server) handleNextHeat() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Get the currently racing group + currentGroup, err := s.db.GetCurrentRacingGroup() + if err != nil { + s.logger.Error("Failed to get current racing group", "error", err) + http.Error(w, "Failed to get current racing group", http.StatusInternalServerError) + return + } + + // Get current heat number + currentHeatNum, err := s.db.GetCurrentHeatNumber(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get current heat number", "error", err) + http.Error(w, "Failed to get current heat number", http.StatusInternalServerError) + return + } + + // Get heats for the group + heats, err := s.db.GetHeats(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get heats", "error", err) + http.Error(w, "Failed to get heats", http.StatusInternalServerError) + return + } + + // Check if we're already at the last heat + if currentHeatNum >= len(heats) { + http.Error(w, "Already at the last heat", http.StatusBadRequest) + return + } + + // Advance to next heat + if err := s.db.SetCurrentHeatNumber(currentGroup.ID, currentHeatNum+1); err != nil { + s.logger.Error("Failed to set current heat number", "error", err) + http.Error(w, "Failed to advance to next heat", http.StatusInternalServerError) + return + } + + // Reset the clock + if err := s.clock.Reset(); err != nil { + s.logger.Error("Failed to reset clock", "error", err) + } + + // Broadcast event to admin page + select { + case s.adminEvents <- "heat-changed": + // Event sent + default: + // Channel full, non-blocking + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) + } +} + +// handlePreviousHeat goes back to the previous heat +func (s *Server) handlePreviousHeat() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Get the currently racing group + currentGroup, err := s.db.GetCurrentRacingGroup() + if err != nil { + s.logger.Error("Failed to get current racing group", "error", err) + http.Error(w, "Failed to get current racing group", http.StatusInternalServerError) + return + } + + // Get current heat number + currentHeatNum, err := s.db.GetCurrentHeatNumber(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get current heat number", "error", err) + http.Error(w, "Failed to get current heat number", http.StatusInternalServerError) + return + } + + // Check if we're already at the first heat + if currentHeatNum <= 1 { + http.Error(w, "Already at the first heat", http.StatusBadRequest) + return + } + + // Go back to previous heat + if err := s.db.SetCurrentHeatNumber(currentGroup.ID, currentHeatNum-1); err != nil { + s.logger.Error("Failed to set current heat number", "error", err) + http.Error(w, "Failed to go back to previous heat", http.StatusInternalServerError) + return + } + + // Reset the clock + if err := s.clock.Reset(); err != nil { + s.logger.Error("Failed to reset clock", "error", err) + } + + // Broadcast event to admin page + select { + case s.adminEvents <- "heat-changed": + // Event sent + default: + // Channel full, non-blocking + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) + } +} + +// handleSaveHeatResult saves the result of a heat +func (s *Server) handleSaveHeatResult() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Parse request body + var request struct { + GroupID int64 `json:"group_id"` + HeatNumber int `json:"heat_number"` + Lane1Time float64 `json:"lane1_time"` + Lane1Position int `json:"lane1_position"` + Lane2Time float64 `json:"lane2_time"` + Lane2Position int `json:"lane2_position"` + Lane3Time float64 `json:"lane3_time"` + Lane3Position int `json:"lane3_position"` + Lane4Time float64 `json:"lane4_time"` + Lane4Position int `json:"lane4_position"` + } + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // Create heat result + result := models.HeatResult{ + GroupID: request.GroupID, + HeatNumber: request.HeatNumber, + Lane1Time: request.Lane1Time, + Lane1Position: request.Lane1Position, + Lane2Time: request.Lane2Time, + Lane2Position: request.Lane2Position, + Lane3Time: request.Lane3Time, + Lane3Position: request.Lane3Position, + Lane4Time: request.Lane4Time, + Lane4Position: request.Lane4Position, + } + + // Save heat result + if err := s.db.SaveHeatResult(result); err != nil { + s.logger.Error("Failed to save heat result", "error", err) + http.Error(w, "Failed to save heat result", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) + } +} + +// handleRerunHeat marks a heat for rerun +func (s *Server) handleRerunHeat() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Get the currently racing group + currentGroup, err := s.db.GetCurrentRacingGroup() + if err != nil { + s.logger.Error("Failed to get current racing group", "error", err) + http.Error(w, "Failed to get current racing group", http.StatusInternalServerError) + return + } + + // Get current heat number + currentHeatNum, err := s.db.GetCurrentHeatNumber(currentGroup.ID) + if err != nil { + s.logger.Error("Failed to get current heat number", "error", err) + http.Error(w, "Failed to get current heat number", http.StatusInternalServerError) + return + } + + // Delete any existing result for this heat + if err := s.db.DeleteHeatResult(currentGroup.ID, currentHeatNum); err != nil { + s.logger.Error("Failed to delete heat result", "error", err) + http.Error(w, "Failed to mark heat for rerun", http.StatusInternalServerError) + return + } + + // Reset the clock + if err := s.clock.Reset(); err != nil { + s.logger.Error("Failed to reset clock", "error", err) + } + + // Broadcast event to admin page + select { + case s.adminEvents <- "heat-rerun": + // Event sent + default: + // Channel full, non-blocking + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) + } +} + +// handleSetRacingGroup sets the currently racing group +func (s *Server) handleSetRacingGroup() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Parse request body + var request struct { + GroupID int64 `json:"group_id"` + } + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // Set current racing group + if err := s.db.SetCurrentRacingGroup(request.GroupID); err != nil { + s.logger.Error("Failed to set current racing group", "error", err) + http.Error(w, "Failed to set current racing group", http.StatusInternalServerError) + return + } + + // Reset the clock + if err := s.clock.Reset(); err != nil { + s.logger.Error("Failed to reset clock", "error", err) + } + + // Broadcast event to admin page + select { + case s.adminEvents <- "group-changed": + // Event sent + default: + // Channel full, non-blocking + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) + } +} diff --git a/web/templates/race_manage.templ b/web/templates/race_manage.templ new file mode 100644 index 0000000..2a35d6f --- /dev/null +++ b/web/templates/race_manage.templ @@ -0,0 +1,449 @@ +package templates + +import ( + "track-gopher/models" + "fmt" + "strconv" +) + +// RaceManage renders the race management view +templ RaceManage(groups []models.Group, currentGroup models.Group, heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) { + @Layout("Race Management") { +
+
+
+
+
+

Race Controls

+
+
+
+ + +
+ +
+ + + Heat { strconv.Itoa(currentHeatNum) } of { strconv.Itoa(len(heats)) } + + +
+ +
+ + + +
+
+
+
+ +
+
+
+

Timer

+
+
+
+
0.000
+
Ready
+
+ +
+
+ Gate Status: Unknown +
+
+
+
+
+
+ +
+
+
+
+

Current Heat

+
+
+
+ @raceManageCurrentHeat(heats, racers, currentHeatNum, results) +
+
+
+
+
+ +
+
+
+
+

Next Heat

+
+
+ if currentHeatNum < len(heats) { + @raceManageNextHeat(heats, racers, currentHeatNum+1) + } else { +
No more heats in this group
+ } +
+
+
+ +
+
+
+

Heat Results

+
+
+
+ + + + + + + + + + + + for _, result := range results { + + + + + + + + } + +
HeatLane 1Lane 2Lane 3Lane 4
{ strconv.Itoa(result.HeatNumber) }{ fmt.Sprintf("%.3f (#%d)", result.Lane1Time, result.Lane1Position) }{ fmt.Sprintf("%.3f (#%d)", result.Lane2Time, result.Lane2Position) }{ fmt.Sprintf("%.3f (#%d)", result.Lane3Time, result.Lane3Position) }{ fmt.Sprintf("%.3f (#%d)", result.Lane4Time, result.Lane4Position) }
+
+
+
+
+
+
+ + + } +} + +// Helper template for displaying current heat in the management view +templ raceManageCurrentHeat(heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) { + {{ + // Find current heat + var currentHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == currentHeatNum { + currentHeat = heat + break + } + } + + // Find heat result if available + var currentResult *models.HeatResult + for _, result := range results { + if result.HeatNumber == currentHeatNum { + currentResult = &result + break + } + } + }} + +
+ + + + + + + + + + + + if currentHeat.Lane1ID != nil { + @raceManageLaneRow(1, *currentHeat.Lane1ID, racers, currentResult) + } + if currentHeat.Lane2ID != nil { + @raceManageLaneRow(2, *currentHeat.Lane2ID, racers, currentResult) + } + if currentHeat.Lane3ID != nil { + @raceManageLaneRow(3, *currentHeat.Lane3ID, racers, currentResult) + } + if currentHeat.Lane4ID != nil { + @raceManageLaneRow(4, *currentHeat.Lane4ID, racers, currentResult) + } + +
LaneRacerCar #TimePosition
+
+} + +// Helper template for displaying a lane row in the management view +templ raceManageLaneRow(lane int, racerID int64, racers []models.Racer, result *models.HeatResult) { + {{ + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + + // Get time and position from result if available + var time float64 + var position int + var hasResult bool + + if result != nil { + hasResult = true + switch lane { + case 1: + time = result.Lane1Time + position = result.Lane1Position + case 2: + time = result.Lane2Time + position = result.Lane2Position + case 3: + time = result.Lane3Time + position = result.Lane3Position + case 4: + time = result.Lane4Time + position = result.Lane4Position + } + } + }} + + + { strconv.Itoa(lane) } + { racer.FirstName } { racer.LastName } + { racer.CarNumber } + + if hasResult { + { fmt.Sprintf("%.3f", time) } + } else { + --.- + } + + + if hasResult { + { strconv.Itoa(position) } + } else { + - + } + + +} + +// Helper template for displaying next heat in the management view +templ raceManageNextHeat(heats []models.Heat, racers []models.Racer, heatNum int) { + {{ + // Find the heat + var nextHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == heatNum { + nextHeat = heat + break + } + } + }} + +
+ + + + + + + + + + if nextHeat.Lane1ID != nil { + @raceManageNextHeatRow(1, *nextHeat.Lane1ID, racers) + } + if nextHeat.Lane2ID != nil { + @raceManageNextHeatRow(2, *nextHeat.Lane2ID, racers) + } + if nextHeat.Lane3ID != nil { + @raceManageNextHeatRow(3, *nextHeat.Lane3ID, racers) + } + if nextHeat.Lane4ID != nil { + @raceManageNextHeatRow(4, *nextHeat.Lane4ID, racers) + } + +
LaneRacerCar #
+
+} + +// Helper template for displaying a row in the next heat preview +templ raceManageNextHeatRow(lane int, racerID int64, racers []models.Racer) { + {{ + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + }} + + + { strconv.Itoa(lane) } + { racer.FirstName } { racer.LastName } + { racer.CarNumber } + +} \ No newline at end of file diff --git a/web/templates/race_manage_templ.go b/web/templates/race_manage_templ.go new file mode 100644 index 0000000..b88c7d9 --- /dev/null +++ b/web/templates/race_manage_templ.go @@ -0,0 +1,669 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "strconv" + "track-gopher/models" +) + +// RaceManage renders the race management view +func RaceManage(groups []models.Group, currentGroup models.Group, heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Race Controls

Heat ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(currentHeatNum)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 36, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " of ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(heats))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 36, Col: 111} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

Timer

0.000
Ready
Gate Status: Unknown

Current Heat

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = raceManageCurrentHeat(heats, racers, currentHeatNum, results).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Next Heat

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if currentHeatNum < len(heats) { + templ_7745c5c3_Err = raceManageNextHeat(heats, racers, currentHeatNum+1).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
No more heats in this group
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Heat Results

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, result := range results { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
HeatLane 1Lane 2Lane 3Lane 4
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(result.HeatNumber)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 130, Col: 85} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f (#%d)", result.Lane1Time, result.Lane1Position)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 131, Col: 119} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f (#%d)", result.Lane2Time, result.Lane2Position)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 132, Col: 119} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f (#%d)", result.Lane3Time, result.Lane3Position)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 133, Col: 119} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f (#%d)", result.Lane4Time, result.Lane4Position)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 134, Col: 119} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Race Management").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying current heat in the management view +func raceManageCurrentHeat(heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find current heat + var currentHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == currentHeatNum { + currentHeat = heat + break + } + } + + // Find heat result if available + var currentResult *models.HeatResult + for _, result := range results { + if result.HeatNumber == currentHeatNum { + currentResult = &result + break + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if currentHeat.Lane1ID != nil { + templ_7745c5c3_Err = raceManageLaneRow(1, *currentHeat.Lane1ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if currentHeat.Lane2ID != nil { + templ_7745c5c3_Err = raceManageLaneRow(2, *currentHeat.Lane2ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if currentHeat.Lane3ID != nil { + templ_7745c5c3_Err = raceManageLaneRow(3, *currentHeat.Lane3ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if currentHeat.Lane4ID != nil { + templ_7745c5c3_Err = raceManageLaneRow(4, *currentHeat.Lane4ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
LaneRacerCar #TimePosition
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying a lane row in the management view +func raceManageLaneRow(lane int, racerID int64, racers []models.Racer, result *models.HeatResult) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var13 := templ.GetChildren(ctx) + if templ_7745c5c3_Var13 == nil { + templ_7745c5c3_Var13 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + + // Get time and position from result if available + var time float64 + var position int + var hasResult bool + + if result != nil { + hasResult = true + switch lane { + case 1: + time = result.Lane1Time + position = result.Lane1Position + case 2: + time = result.Lane2Time + position = result.Lane2Position + case 3: + time = result.Lane3Time + position = result.Lane3Position + case 4: + time = result.Lane4Time + position = result.Lane4Position + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(lane)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 371, Col: 32} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 372, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(racer.LastName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 372, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 373, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if hasResult { + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f", time)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 376, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "--.-") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if hasResult { + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(position)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 383, Col: 40} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "-") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying next heat in the management view +func raceManageNextHeat(heats []models.Heat, racers []models.Racer, heatNum int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var22 := templ.GetChildren(ctx) + if templ_7745c5c3_Var22 == nil { + templ_7745c5c3_Var22 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find the heat + var nextHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == heatNum { + nextHeat = heat + break + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if nextHeat.Lane1ID != nil { + templ_7745c5c3_Err = raceManageNextHeatRow(1, *nextHeat.Lane1ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if nextHeat.Lane2ID != nil { + templ_7745c5c3_Err = raceManageNextHeatRow(2, *nextHeat.Lane2ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if nextHeat.Lane3ID != nil { + templ_7745c5c3_Err = raceManageNextHeatRow(3, *nextHeat.Lane3ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if nextHeat.Lane4ID != nil { + templ_7745c5c3_Err = raceManageNextHeatRow(4, *nextHeat.Lane4ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
LaneRacerCar #
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying a row in the next heat preview +func raceManageNextHeatRow(lane int, racerID int64, racers []models.Racer) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var23 := templ.GetChildren(ctx) + if templ_7745c5c3_Var23 == nil { + templ_7745c5c3_Var23 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(lane)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 445, Col: 32} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 446, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(racer.LastName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 446, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_manage.templ`, Line: 447, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/web/templates/race_public.templ b/web/templates/race_public.templ new file mode 100644 index 0000000..a21839f --- /dev/null +++ b/web/templates/race_public.templ @@ -0,0 +1,297 @@ +package templates + +import ( + "track-gopher/models" + "fmt" + "strconv" +) + +// RacePublic renders the public race view +templ RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) { + @Layout("Race - " + currentGroup.Name) { +
+
+
+
+
+

{ currentGroup.Name } - Heat { strconv.Itoa(currentHeatNum) } of { strconv.Itoa(len(heats)) }

+
+
+
+
+
+
+
0.000
+
Ready
+
+ +
+ @raceCurrentHeatLanes(heats, racers, currentHeatNum, results) +
+
+
+
+
+
+
+
+ +
+
+
+
+

Next Heat

+
+
+ if currentHeatNum < len(heats) { + @raceNextHeatPreview(heats, racers, currentHeatNum+1) + } else { +
No more heats in this group
+ } +
+
+
+
+
+
+

Upcoming Heat

+
+
+ if currentHeatNum+1 < len(heats) { + @raceNextHeatPreview(heats, racers, currentHeatNum+2) + } else { +
No more heats in this group
+ } +
+
+
+
+
+ + + } +} + +// Helper template for displaying current heat lanes +templ raceCurrentHeatLanes(heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) { + {{ + // Find current heat + var currentHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == currentHeatNum { + currentHeat = heat + break + } + } + + // Find heat result if available + var currentResult *models.HeatResult + for _, result := range results { + if result.HeatNumber == currentHeatNum { + currentResult = &result + break + } + } + }} + +
+ if currentHeat.Lane1ID != nil { + @raceLaneCard(1, *currentHeat.Lane1ID, racers, currentResult) + } + if currentHeat.Lane2ID != nil { + @raceLaneCard(2, *currentHeat.Lane2ID, racers, currentResult) + } + if currentHeat.Lane3ID != nil { + @raceLaneCard(3, *currentHeat.Lane3ID, racers, currentResult) + } + if currentHeat.Lane4ID != nil { + @raceLaneCard(4, *currentHeat.Lane4ID, racers, currentResult) + } +
+} + +// Helper template for displaying a lane card +templ raceLaneCard(lane int, racerID int64, racers []models.Racer, result *models.HeatResult) { + {{ + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + + // Get time and position from result if available + var time float64 + var position int + var hasResult bool + + if result != nil { + hasResult = true + switch lane { + case 1: + time = result.Lane1Time + position = result.Lane1Position + case 2: + time = result.Lane2Time + position = result.Lane2Position + case 3: + time = result.Lane3Time + position = result.Lane3Position + case 4: + time = result.Lane4Time + position = result.Lane4Position + } + } + }} + +
+
+
+

Lane { strconv.Itoa(lane) }

+
+
+
{ racer.FirstName } { racer.LastName }
+

+ Car #: { racer.CarNumber }
+ Weight: { fmt.Sprintf("%.1f oz", racer.CarWeight) } +

+
+
+
+
+
Time
+
+ if hasResult { + { fmt.Sprintf("%.3f", time) } + } else { + --.- + } +
+
+
+
+
+
Position
+
+ if hasResult { + { strconv.Itoa(position) } + } else { + - + } +
+
+
+
+
+
+
+
+} + +// Helper template for displaying next heat preview +templ raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum int) { + {{ + // Find the heat + var nextHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == heatNum { + nextHeat = heat + break + } + } + }} + +

Heat { strconv.Itoa(heatNum) }

+
+ + + + + + + + + + if nextHeat.Lane1ID != nil { + @raceNextHeatRow(1, *nextHeat.Lane1ID, racers) + } + if nextHeat.Lane2ID != nil { + @raceNextHeatRow(2, *nextHeat.Lane2ID, racers) + } + if nextHeat.Lane3ID != nil { + @raceNextHeatRow(3, *nextHeat.Lane3ID, racers) + } + if nextHeat.Lane4ID != nil { + @raceNextHeatRow(4, *nextHeat.Lane4ID, racers) + } + +
LaneRacerCar #
+
+} + +// Helper template for displaying a row in the next heat preview +templ raceNextHeatRow(lane int, racerID int64, racers []models.Racer) { + {{ + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + }} + + + { strconv.Itoa(lane) } + { racer.FirstName } { racer.LastName } + { racer.CarNumber } + +} \ No newline at end of file diff --git a/web/templates/race_public_templ.go b/web/templates/race_public_templ.go new file mode 100644 index 0000000..b1b230a --- /dev/null +++ b/web/templates/race_public_templ.go @@ -0,0 +1,582 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "strconv" + "track-gopher/models" +) + +// RacePublic renders the public race view +func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(currentGroup.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Heat ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(currentHeatNum)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 116} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " of ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(heats))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 148} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

0.000
Ready
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = raceCurrentHeatLanes(heats, racers, currentHeatNum, results).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

Next Heat

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if currentHeatNum < len(heats) { + templ_7745c5c3_Err = raceNextHeatPreview(heats, racers, currentHeatNum+1).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
No more heats in this group
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

Upcoming Heat

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if currentHeatNum+1 < len(heats) { + templ_7745c5c3_Err = raceNextHeatPreview(heats, racers, currentHeatNum+2).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
No more heats in this group
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Race - "+currentGroup.Name).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying current heat lanes +func raceCurrentHeatLanes(heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find current heat + var currentHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == currentHeatNum { + currentHeat = heat + break + } + } + + // Find heat result if available + var currentResult *models.HeatResult + for _, result := range results { + if result.HeatNumber == currentHeatNum { + currentResult = &result + break + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if currentHeat.Lane1ID != nil { + templ_7745c5c3_Err = raceLaneCard(1, *currentHeat.Lane1ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if currentHeat.Lane2ID != nil { + templ_7745c5c3_Err = raceLaneCard(2, *currentHeat.Lane2ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if currentHeat.Lane3ID != nil { + templ_7745c5c3_Err = raceLaneCard(3, *currentHeat.Lane3ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if currentHeat.Lane4ID != nil { + templ_7745c5c3_Err = raceLaneCard(4, *currentHeat.Lane4ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying a lane card +func raceLaneCard(lane int, racerID int64, racers []models.Racer, result *models.HeatResult) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + + // Get time and position from result if available + var time float64 + var position int + var hasResult bool + + if result != nil { + hasResult = true + switch lane { + case 1: + time = result.Lane1Time + position = result.Lane1Position + case 2: + time = result.Lane2Time + position = result.Lane2Position + case 3: + time = result.Lane3Time + position = result.Lane3Position + case 4: + time = result.Lane4Time + position = result.Lane4Position + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

Lane ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(lane)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 197, Col: 58} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 200, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(racer.LastName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 200, Col: 75} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

Car #: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 202, Col: 61} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
Weight: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f oz", racer.CarWeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 203, Col: 86} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

Time
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if hasResult { + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f", time)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 212, Col: 67} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "--.-") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
Position
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if hasResult { + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(position)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 224, Col: 64} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "-") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying next heat preview +func raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var17 := templ.GetChildren(ctx) + if templ_7745c5c3_Var17 == nil { + templ_7745c5c3_Var17 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find the heat + var nextHeat models.Heat + for _, heat := range heats { + if heat.HeatNum == heatNum { + nextHeat = heat + break + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

Heat ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(heatNum)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 251, Col: 49} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if nextHeat.Lane1ID != nil { + templ_7745c5c3_Err = raceNextHeatRow(1, *nextHeat.Lane1ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if nextHeat.Lane2ID != nil { + templ_7745c5c3_Err = raceNextHeatRow(2, *nextHeat.Lane2ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if nextHeat.Lane3ID != nil { + templ_7745c5c3_Err = raceNextHeatRow(3, *nextHeat.Lane3ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if nextHeat.Lane4ID != nil { + templ_7745c5c3_Err = raceNextHeatRow(4, *nextHeat.Lane4ID, racers).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
LaneRacerCar #
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Helper template for displaying a row in the next heat preview +func raceNextHeatRow(lane int, racerID int64, racers []models.Racer) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + // Find racer + var racer models.Racer + for _, r := range racers { + if r.ID == racerID { + racer = r + break + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(lane)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 293, Col: 32} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 294, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(racer.LastName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 294, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 295, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate