Generate heats

main
DustyP 9 months ago
parent 300c5c1f1d
commit edc8a378a9

@ -16,7 +16,7 @@ type DB struct {
logger *slog.Logger
}
// New creates a new database connection
// NewDB creates a new database connection
func New(dbPath string) (*DB, error) {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
@ -112,5 +112,170 @@ func (db *DB) initSchema() error {
return fmt.Errorf("failed to create race_results table: %w", err)
}
// Create heats table
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS heats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER NOT NULL,
heat_num INTEGER NOT NULL,
lane1_id INTEGER,
lane2_id INTEGER,
lane3_id INTEGER,
lane4_id INTEGER,
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE,
FOREIGN KEY (lane1_id) REFERENCES racers(id) ON DELETE SET NULL,
FOREIGN KEY (lane2_id) REFERENCES racers(id) ON DELETE SET NULL,
FOREIGN KEY (lane3_id) REFERENCES racers(id) ON DELETE SET NULL,
FOREIGN KEY (lane4_id) REFERENCES racers(id) ON DELETE SET NULL
)
`)
if err != nil {
return fmt.Errorf("failed to create heats table: %w", err)
}
return nil
}
// Close closes the database connection
func (db *DB) Close() error {
return db.DB.Close()
}
// Group represents a group of racers
type Group struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
// Racer represents a racer in the derby
type Racer struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
CarNumber string `json:"car_number"`
CarWeight float64 `json:"car_weight"`
GroupID int64 `json:"group_id"`
}
// Heat represents a single race with 4 lanes
type Heat struct {
ID int64 `json:"id"`
GroupID int64 `json:"group_id"`
HeatNum int `json:"heat_num"`
Lane1ID *int64 `json:"lane1_id"`
Lane2ID *int64 `json:"lane2_id"`
Lane3ID *int64 `json:"lane3_id"`
Lane4ID *int64 `json:"lane4_id"`
}
// SaveHeats saves a list of heats for a group
func (db *DB) SaveHeats(groupID int64, heats []Heat) error {
// Start a transaction
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
// Delete existing heats for this group
_, err = tx.Exec("DELETE FROM heats WHERE group_id = ?", groupID)
if err != nil {
return err
}
// Insert new heats
stmt, err := tx.Prepare(`
INSERT INTO heats (group_id, heat_num, lane1_id, lane2_id, lane3_id, lane4_id)
VALUES (?, ?, ?, ?, ?, ?)
`)
if err != nil {
return err
}
defer stmt.Close()
for _, heat := range heats {
_, err = stmt.Exec(
groupID,
heat.HeatNum,
nullableInt64(heat.Lane1ID),
nullableInt64(heat.Lane2ID),
nullableInt64(heat.Lane3ID),
nullableInt64(heat.Lane4ID),
)
if err != nil {
return err
}
}
// Commit the transaction
return tx.Commit()
}
// Helper function to handle nullable int64 values
func nullableInt64(i *int64) interface{} {
if i == nil {
return nil
}
return *i
}
// GetHeats retrieves all heats for a group
func (db *DB) GetHeats(groupID int64) ([]Heat, error) {
rows, err := db.Query(`
SELECT id, group_id, heat_num, lane1_id, lane2_id, lane3_id, lane4_id
FROM heats
WHERE group_id = ?
ORDER BY heat_num
`, groupID)
if err != nil {
return nil, err
}
defer rows.Close()
var heats []Heat
for rows.Next() {
var heat Heat
var lane1, lane2, lane3, lane4 sql.NullInt64
err := rows.Scan(
&heat.ID,
&heat.GroupID,
&heat.HeatNum,
&lane1,
&lane2,
&lane3,
&lane4,
)
if err != nil {
return nil, err
}
if lane1.Valid {
val := lane1.Int64
heat.Lane1ID = &val
}
if lane2.Valid {
val := lane2.Int64
heat.Lane2ID = &val
}
if lane3.Valid {
val := lane3.Int64
heat.Lane3ID = &val
}
if lane4.Valid {
val := lane4.Int64
heat.Lane4ID = &val
}
heats = append(heats, heat)
}
return heats, nil
}
// DeleteHeats deletes all heats for a group
func (db *DB) DeleteHeats(groupID int64) error {
_, err := db.Exec("DELETE FROM heats WHERE group_id = ?", groupID)
return err
}

@ -0,0 +1,181 @@
package derby
import (
"math/rand"
"time"
)
// Racer represents a racer for heat generation
type Racer struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
CarNumber string `json:"car_number"`
}
// Heat represents a single race with 4 lanes
type Heat struct {
HeatNum int `json:"heat_num"`
Lane1ID *int64 `json:"lane1_id"`
Lane2ID *int64 `json:"lane2_id"`
Lane3ID *int64 `json:"lane3_id"`
Lane4ID *int64 `json:"lane4_id"`
}
func CreateRacer(id int64, firstName string, lastName string, carNumber string) Racer {
return Racer{ID: id, FirstName: firstName, LastName: lastName, CarNumber: carNumber}
}
// GenerateHeats creates a set of heats for a group of racers
// Each racer should race in each lane once, with different opponents when possible
func GenerateHeats(racers []Racer) []Heat {
// If no racers, return empty heats
if len(racers) == 0 {
return []Heat{}
}
// Seed the random number generator
rand.Seed(time.Now().UnixNano())
// Shuffle racers to randomize initial order
shuffledRacers := make([]Racer, len(racers))
copy(shuffledRacers, racers)
rand.Shuffle(len(shuffledRacers), func(i, j int) {
shuffledRacers[i], shuffledRacers[j] = shuffledRacers[j], shuffledRacers[i]
})
// Track which lanes each racer has used
racerLanes := make(map[int64]map[int]bool)
for _, racer := range racers {
racerLanes[racer.ID] = make(map[int]bool)
}
// Track which racers have raced against each other
racerOpponents := make(map[int64]map[int64]bool)
for _, racer := range racers {
racerOpponents[racer.ID] = make(map[int64]bool)
}
// Calculate number of heats needed
// Each racer needs to race in 4 lanes, so total races = (racers * 4) / 4 = racers
// But we need to round up to ensure all racers get all lanes
numHeats := len(racers)
if len(racers) < 4 {
numHeats = 1
} else if len(racers)%4 != 0 {
// Add extra heats to ensure all racers get all lanes
numHeats = (len(racers) + 3) / 4 * 4
}
// Generate heats
heats := make([]Heat, 0, numHeats)
heatNum := 1
// Create a queue of racers that need to race in each lane
laneQueues := make([][]int64, 4)
for i := 0; i < 4; i++ {
laneQueues[i] = make([]int64, 0, len(racers))
for _, racer := range shuffledRacers {
laneQueues[i] = append(laneQueues[i], racer.ID)
}
// Shuffle each lane queue differently
rand.Shuffle(len(laneQueues[i]), func(j, k int) {
laneQueues[i][j], laneQueues[i][k] = laneQueues[i][k], laneQueues[i][j]
})
}
// Keep generating heats until all racers have raced in all lanes
for {
// Check if all racers have raced in all lanes
allDone := true
for _, racer := range racers {
if len(racerLanes[racer.ID]) < 4 {
allDone = false
break
}
}
if allDone {
break
}
// Create a new heat
heat := Heat{
HeatNum: heatNum,
}
heatNum++
// Fill lanes
filledLanes := 0
usedRacers := make(map[int64]bool)
// Try to fill each lane
for lane := 1; lane <= 4; lane++ {
// Find a racer for this lane
var selectedRacer *int64 = nil
// First try racers who haven't used this lane yet
for i := 0; i < len(laneQueues[lane-1]); i++ {
racerID := laneQueues[lane-1][i]
// Skip if racer already in this heat
if usedRacers[racerID] {
continue
}
// Skip if racer already used this lane
if racerLanes[racerID][lane] {
continue
}
// Use this racer
selectedRacer = &racerID
// Remove from queue
laneQueues[lane-1] = append(laneQueues[lane-1][:i], laneQueues[lane-1][i+1:]...)
break
}
// If no racer found, leave lane empty
if selectedRacer != nil {
// Mark lane as used for this racer
racerLanes[*selectedRacer][lane] = true
// Mark racer as used in this heat
usedRacers[*selectedRacer] = true
// Update opponents
for otherRacerID := range usedRacers {
if otherRacerID != *selectedRacer {
racerOpponents[*selectedRacer][otherRacerID] = true
racerOpponents[otherRacerID][*selectedRacer] = true
}
}
filledLanes++
}
// Set lane in heat
switch lane {
case 1:
heat.Lane1ID = selectedRacer
case 2:
heat.Lane2ID = selectedRacer
case 3:
heat.Lane3ID = selectedRacer
case 4:
heat.Lane4ID = selectedRacer
}
}
// Only add heat if at least one lane is filled
if filledLanes > 0 {
heats = append(heats, heat)
} else {
// If we couldn't fill any lanes, we're done
break
}
}
return heats
}

@ -129,6 +129,15 @@ func (s *Server) routes() {
r.Delete("/{id}", s.handleDeleteRacer())
})
// Add heats page route
s.router.Get("/heats", s.handleHeats())
// Add heats API routes
s.router.Route("/api/heats", func(r chi.Router) {
r.Post("/generate", s.handleGenerateHeats())
r.Post("/save", s.handleSaveHeats())
})
// Main page
s.router.Get("/", s.handleIndex())
}
@ -656,3 +665,155 @@ func (s *Server) handleDeleteRacer() http.HandlerFunc {
fmt.Fprintf(w, `{"success":true}`)
}
}
// handleHeats renders the heats page
func (s *Server) handleHeats() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get groups from database
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 racers from database
racers, err := s.db.GetRacers()
if err != nil {
s.logger.Error("Failed to get racers", "error", err)
http.Error(w, "Failed to get racers", http.StatusInternalServerError)
return
}
// Get selected group ID from query parameter
selectedGroupID := int64(0)
groupIDStr := r.URL.Query().Get("group_id")
if groupIDStr != "" {
groupID, err := strconv.ParseInt(groupIDStr, 10, 64)
if err == nil {
selectedGroupID = groupID
}
}
// Render template
component := templates.Heats(groups, racers, selectedGroupID)
if err := component.Render(r.Context(), w); err != nil {
s.logger.Error("Failed to render heats template", "error", err)
http.Error(w, "Failed to render page", http.StatusInternalServerError)
}
}
}
// handleGenerateHeats generates heats for a group
func (s *Server) handleGenerateHeats() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get group ID from query parameter
groupIDStr := r.URL.Query().Get("group_id")
groupID, err := strconv.ParseInt(groupIDStr, 10, 64)
if err != nil {
http.Error(w, "Invalid group ID", http.StatusBadRequest)
return
}
// Get racers for this group
allRacers, err := s.db.GetRacers()
if err != nil {
s.logger.Error("Failed to get racers", "error", err)
http.Error(w, "Failed to get racers", http.StatusInternalServerError)
return
}
// Filter racers by group
var groupRacers []db.Racer
for _, racer := range allRacers {
if racer.GroupID == groupID {
groupRacers = append(groupRacers, racer)
}
}
// Convert to derby racers
derbyRacers := make([]derby.Racer, len(groupRacers))
for i, racer := range groupRacers {
derbyRacers[i] = derby.Racer{
ID: racer.ID,
FirstName: racer.FirstName,
LastName: racer.LastName,
CarNumber: racer.CarNumber,
}
}
// Render the generated heats
component := templates.GeneratedHeats(groupRacers)
if err := component.Render(r.Context(), w); err != nil {
s.logger.Error("Failed to render heats", "error", err)
http.Error(w, "Failed to render heats", http.StatusInternalServerError)
}
}
}
// handleSaveHeats saves heats for a group
func (s *Server) handleSaveHeats() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get group ID from query parameter
groupIDStr := r.URL.Query().Get("group_id")
groupID, err := strconv.ParseInt(groupIDStr, 10, 64)
if err != nil {
http.Error(w, "Invalid group ID", http.StatusBadRequest)
return
}
// Get racers for this group
allRacers, err := s.db.GetRacers()
if err != nil {
s.logger.Error("Failed to get racers", "error", err)
http.Error(w, "Failed to get racers", http.StatusInternalServerError)
return
}
// Filter racers by group
var groupRacers []db.Racer
for _, racer := range allRacers {
if racer.GroupID == groupID {
groupRacers = append(groupRacers, racer)
}
}
// Convert to derby racers
derbyRacers := make([]derby.Racer, len(groupRacers))
for i, racer := range groupRacers {
derbyRacers[i] = derby.Racer{
ID: racer.ID,
FirstName: racer.FirstName,
LastName: racer.LastName,
CarNumber: racer.CarNumber,
}
}
// Generate heats
derbyHeats := derby.GenerateHeats(derbyRacers)
// Convert to database heats
dbHeats := make([]db.Heat, len(derbyHeats))
for i, heat := range derbyHeats {
dbHeats[i] = db.Heat{
GroupID: groupID,
HeatNum: heat.HeatNum,
Lane1ID: heat.Lane1ID,
Lane2ID: heat.Lane2ID,
Lane3ID: heat.Lane3ID,
Lane4ID: heat.Lane4ID,
}
}
// Save heats to database
if err := s.db.SaveHeats(groupID, dbHeats); err != nil {
s.logger.Error("Failed to save heats", "error", err)
http.Error(w, "Failed to save heats", http.StatusInternalServerError)
return
}
// Return success message
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<div class="bg-green-100 p-4 rounded-lg mb-4"><p class="text-green-800">Heats saved successfully!</p></div>`))
}
}

@ -0,0 +1,182 @@
package templates
import (
"strconv"
"track-gopher/db"
"track-gopher/derby"
)
templ Heats(groups []db.Group, racers []db.Racer, selectedGroupID int64) {
@Layout("Race Heats") {
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-6">Race Heats Generator</h1>
<div class="mb-6">
<label for="group-select" class="block text-sm font-medium text-gray-700 mb-2">Select Group</label>
<select
id="group-select"
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
hx-get="/heats"
hx-target="#heats-container"
hx-trigger="change"
hx-include="this"
name="group_id"
>
<option value="">Select a group</option>
for _, group := range groups {
<option value={ strconv.FormatInt(group.ID, 10) } selected?={ group.ID == selectedGroupID }>
{ group.Name }
</option>
}
</select>
</div>
<div id="heats-container">
if selectedGroupID > 0 {
@HeatsContent(selectedGroupID, groups, racers)
} else {
<div class="bg-gray-100 p-6 rounded-lg">
<p class="text-gray-600">Please select a group to generate heats.</p>
</div>
}
</div>
</div>
}
}
templ HeatsContent(groupID int64, groups []db.Group, allRacers []db.Racer) {
// Filter racers by group
var groupRacers []db.Racer
for _, racer := range allRacers {
if racer.GroupID == groupID {
groupRacers = append(groupRacers, racer)
}
}
<div class="bg-white shadow-md rounded-lg p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold">
Heats for { getGroupName(groups, groupID) } ({ strconv.Itoa(len(groupRacers)) } racers)
</h2>
<div>
<button
class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 mr-2"
hx-post={ "/api/heats/generate?group_id=" + strconv.FormatInt(groupID, 10) }
hx-target="#heats-list"
hx-swap="innerHTML"
>
Regenerate Heats
</button>
<button
class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700"
hx-post={ "/api/heats/save?group_id=" + strconv.FormatInt(groupID, 10) }
hx-target="#save-status"
>
Save Heats
</button>
</div>
</div>
<div id="save-status" class="mb-4"></div>
if len(groupRacers) == 0 {
<div class="bg-yellow-100 p-4 rounded-lg mb-6">
<p class="text-yellow-800">No racers in this group. Add racers to generate heats.</p>
</div>
} else {
<div id="heats-list" class="space-y-6">
@GeneratedHeats(groupRacers)
</div>
}
</div>
}
templ GeneratedHeats(racers []db.Racer) {
// Convert db.Racer to derby.Racer for heat generation
var derbyRacers []derby.Racer
for _, racer := range racers {
derbyRacers = append(derbyRacers, derby.CreateRacer(racer.ID, racer.FirstName, racer.LastName, racer.CarNumber))
}
var heats = derby.GenerateHeats(derbyRacers)
if len(heats) == 0 {
<div class="bg-yellow-100 p-4 rounded-lg">
<p class="text-yellow-800">No heats could be generated. Please add more racers.</p>
</div>
} else {
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
for _, heat := range heats {
<div class="bg-gray-50 p-4 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-3">Heat { strconv.Itoa(heat.HeatNum) }</h3>
<table class="w-full">
<thead>
<tr class="bg-gray-200">
<th class="px-2 py-1 text-left">Lane</th>
<th class="px-2 py-1 text-left">Racer</th>
<th class="px-2 py-1 text-left">Car #</th>
</tr>
</thead>
<tbody>
<tr>
<td class="px-2 py-1">1</td>
<td class="px-2 py-1">{ getRacerName(racers, heat.Lane1ID) }</td>
<td class="px-2 py-1">{ getRacerCarNumber(racers, heat.Lane1ID) }</td>
</tr>
<tr>
<td class="px-2 py-1">2</td>
<td class="px-2 py-1">{ getRacerName(racers, heat.Lane2ID) }</td>
<td class="px-2 py-1">{ getRacerCarNumber(racers, heat.Lane2ID) }</td>
</tr>
<tr>
<td class="px-2 py-1">3</td>
<td class="px-2 py-1">{ getRacerName(racers, heat.Lane3ID) }</td>
<td class="px-2 py-1">{ getRacerCarNumber(racers, heat.Lane3ID) }</td>
</tr>
<tr>
<td class="px-2 py-1">4</td>
<td class="px-2 py-1">{ getRacerName(racers, heat.Lane4ID) }</td>
<td class="px-2 py-1">{ getRacerCarNumber(racers, heat.Lane4ID) }</td>
</tr>
</tbody>
</table>
</div>
}
</div>
}
}
func getGroupName(groups []db.Group, groupID int64) string {
for _, group := range groups {
if group.ID == groupID {
return group.Name
}
}
return "Unknown Group"
}
func getRacerName(racers []db.Racer, racerID *int64) string {
if racerID == nil {
return "Empty"
}
for _, racer := range racers {
if racer.ID == *racerID {
return racer.FirstName + " " + racer.LastName
}
}
return "Unknown Racer"
}
func getRacerCarNumber(racers []db.Racer, racerID *int64) string {
if racerID == nil {
return "-"
}
for _, racer := range racers {
if racer.ID == *racerID {
return racer.CarNumber
}
}
return "-"
}

@ -0,0 +1,453 @@
// 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 (
"strconv"
"track-gopher/db"
"track-gopher/derby"
)
func Heats(groups []db.Group, racers []db.Racer, selectedGroupID int64) 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, "<div class=\"container mx-auto px-4 py-8\"><h1 class=\"text-3xl font-bold mb-6\">Race Heats Generator</h1><div class=\"mb-6\"><label for=\"group-select\" class=\"block text-sm font-medium text-gray-700 mb-2\">Select Group</label> <select id=\"group-select\" class=\"block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500\" hx-get=\"/heats\" hx-target=\"#heats-container\" hx-trigger=\"change\" hx-include=\"this\" name=\"group_id\"><option value=\"\">Select a group</option> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, group := range groups {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<option value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(group.ID, 10))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 27, Col: 53}
}
_, 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, 3, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if group.ID == selectedGroupID {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 28, Col: 19}
}
_, 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, 6, "</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</select></div><div id=\"heats-container\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if selectedGroupID > 0 {
templ_7745c5c3_Err = HeatsContent(selectedGroupID, groups, racers).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"bg-gray-100 p-6 rounded-lg\"><p class=\"text-gray-600\">Please select a group to generate heats.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = Layout("Race Heats").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func HeatsContent(groupID int64, groups []db.Group, allRacers []db.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_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "var groupRacers []db.Racer ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, racer := range allRacers {
if racer.GroupID == groupID {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "groupRacers = append(groupRacers, racer)")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"bg-white shadow-md rounded-lg p-6\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-semibold\">Heats for ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(getGroupName(groups, groupID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 59, Col: 45}
}
_, 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, 13, " (")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(groupRacers)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 59, Col: 81}
}
_, 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, 14, " racers)</h2><div><button class=\"bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 mr-2\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs("/api/heats/generate?group_id=" + strconv.FormatInt(groupID, 10))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 64, Col: 79}
}
_, 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, 15, "\" hx-target=\"#heats-list\" hx-swap=\"innerHTML\">Regenerate Heats</button> <button class=\"bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs("/api/heats/save?group_id=" + strconv.FormatInt(groupID, 10))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 72, Col: 75}
}
_, 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, 16, "\" hx-target=\"#save-status\">Save Heats</button></div></div><div id=\"save-status\" class=\"mb-4\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(groupRacers) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"bg-yellow-100 p-4 rounded-lg mb-6\"><p class=\"text-yellow-800\">No racers in this group. Add racers to generate heats.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div id=\"heats-list\" class=\"space-y-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = GeneratedHeats(groupRacers).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func GeneratedHeats(racers []db.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_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "var derbyRacers []derby.Racer ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, racer := range racers {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "derbyRacers = append(derbyRacers, derby.CreateRacer(racer.ID, racer.FirstName, racer.LastName, racer.CarNumber)) ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "var heats = derby.GenerateHeats(derbyRacers) ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(heats) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<div class=\"bg-yellow-100 p-4 rounded-lg\"><p class=\"text-yellow-800\">No heats could be generated. Please add more racers.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, heat := range heats {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<div class=\"bg-gray-50 p-4 rounded-lg shadow\"><h3 class=\"text-lg font-semibold mb-3\">Heat ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(heat.HeatNum))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 111, Col: 77}
}
_, 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, 27, "</h3><table class=\"w-full\"><thead><tr class=\"bg-gray-200\"><th class=\"px-2 py-1 text-left\">Lane</th><th class=\"px-2 py-1 text-left\">Racer</th><th class=\"px-2 py-1 text-left\">Car #</th></tr></thead> <tbody><tr><td class=\"px-2 py-1\">1</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerName(racers, heat.Lane1ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 123, Col: 66}
}
_, 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, 28, "</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerCarNumber(racers, heat.Lane1ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 124, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</td></tr><tr><td class=\"px-2 py-1\">2</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerName(racers, heat.Lane2ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 128, Col: 66}
}
_, 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, 30, "</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerCarNumber(racers, heat.Lane2ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 129, Col: 71}
}
_, 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, 31, "</td></tr><tr><td class=\"px-2 py-1\">3</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerName(racers, heat.Lane3ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 133, Col: 66}
}
_, 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, 32, "</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerCarNumber(racers, heat.Lane3ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 134, Col: 71}
}
_, 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, 33, "</td></tr><tr><td class=\"px-2 py-1\">4</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerName(racers, heat.Lane4ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 138, Col: 66}
}
_, 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, 34, "</td><td class=\"px-2 py-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(getRacerCarNumber(racers, heat.Lane4ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/heats.templ`, Line: 139, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</td></tr></tbody></table></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func getGroupName(groups []db.Group, groupID int64) string {
for _, group := range groups {
if group.ID == groupID {
return group.Name
}
}
return "Unknown Group"
}
func getRacerName(racers []db.Racer, racerID *int64) string {
if racerID == nil {
return "Empty"
}
for _, racer := range racers {
if racer.ID == *racerID {
return racer.FirstName + " " + racer.LastName
}
}
return "Unknown Racer"
}
func getRacerCarNumber(racers []db.Racer, racerID *int64) string {
if racerID == nil {
return "-"
}
for _, racer := range racers {
if racer.ID == *racerID {
return racer.CarNumber
}
}
return "-"
}
var _ = templruntime.GeneratedTemplate

@ -0,0 +1,30 @@
package templates
templ Layout(title string) {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ title } - Derby Race Manager</title>
<link href="/static/css/tailwind.css" rel="stylesheet"/>
<script src="/static/js/htmx.min.js"></script>
</head>
<body class="bg-gray-100 min-h-screen">
<nav class="bg-indigo-600 text-white p-4">
<div class="container mx-auto flex justify-between items-center">
<a href="/" class="text-xl font-bold">Derby Race Manager</a>
<div class="space-x-4">
<a href="/" class="hover:underline">Home</a>
<a href="/admin" class="hover:underline">Admin</a>
<a href="/register" class="hover:underline">Register</a>
<a href="/heats" class="hover:underline">Heats</a>
</div>
</div>
</nav>
<main>
{ children... }
</main>
</body>
</html>
}

@ -0,0 +1,61 @@
// 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"
func Layout(title string) 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_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/layout.templ`, Line: 9, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Derby Race Manager</title><link href=\"/static/css/tailwind.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body class=\"bg-gray-100 min-h-screen\"><nav class=\"bg-indigo-600 text-white p-4\"><div class=\"container mx-auto flex justify-between items-center\"><a href=\"/\" class=\"text-xl font-bold\">Derby Race Manager</a><div class=\"space-x-4\"><a href=\"/\" class=\"hover:underline\">Home</a> <a href=\"/admin\" class=\"hover:underline\">Admin</a> <a href=\"/register\" class=\"hover:underline\">Register</a> <a href=\"/heats\" class=\"hover:underline\">Heats</a></div></div></nav><main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</main></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate
Loading…
Cancel
Save