parent
964432dfc4
commit
300c5c1f1d
@ -0,0 +1,232 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"track-gopher/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Group operations
|
||||||
|
|
||||||
|
// CreateGroup creates a new group
|
||||||
|
func (db *DB) CreateGroup(name, description string) (int64, error) {
|
||||||
|
result, err := db.Exec(
|
||||||
|
"INSERT INTO groups (name, description) VALUES (?, ?)",
|
||||||
|
name, description,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to create group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get last insert ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroups returns all groups
|
||||||
|
func (db *DB) GetGroups() ([]models.Group, error) {
|
||||||
|
rows, err := db.Query("SELECT id, name, description, created_at FROM groups ORDER BY name")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query groups: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var groups []models.Group
|
||||||
|
for rows.Next() {
|
||||||
|
var g models.Group
|
||||||
|
var createdAt string
|
||||||
|
if err := rows.Scan(&g.ID, &g.Name, &g.Description, &createdAt); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan group row: %w", err)
|
||||||
|
}
|
||||||
|
g.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||||
|
groups = append(groups, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error iterating group rows: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroup returns a group by ID
|
||||||
|
func (db *DB) GetGroup(id int64) (*models.Group, error) {
|
||||||
|
var g models.Group
|
||||||
|
var createdAt string
|
||||||
|
err := db.QueryRow(
|
||||||
|
"SELECT id, name, description, created_at FROM groups WHERE id = ?",
|
||||||
|
id,
|
||||||
|
).Scan(&g.ID, &g.Name, &g.Description, &createdAt)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to query group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||||
|
return &g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGroup updates a group
|
||||||
|
func (db *DB) UpdateGroup(id int64, name, description string) error {
|
||||||
|
_, err := db.Exec(
|
||||||
|
"UPDATE groups SET name = ?, description = ? WHERE id = ?",
|
||||||
|
name, description, id,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update group: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteGroup deletes a group
|
||||||
|
func (db *DB) DeleteGroup(id int64) error {
|
||||||
|
_, err := db.Exec("DELETE FROM groups WHERE id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete group: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Racer operations
|
||||||
|
|
||||||
|
// CreateRacer creates a new racer
|
||||||
|
func (db *DB) CreateRacer(firstName, lastName, carNumber string, carWeight float64, groupID int64) (int64, error) {
|
||||||
|
result, err := db.Exec(
|
||||||
|
"INSERT INTO racers (first_name, last_name, car_number, car_weight, group_id) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
firstName, lastName, carNumber, carWeight, groupID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to create racer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get last insert ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRacers returns all racers
|
||||||
|
func (db *DB) GetRacers() ([]models.Racer, error) {
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT r.id, r.first_name, r.last_name, r.car_number, r.car_weight,
|
||||||
|
r.group_id, g.name as group_name, r.created_at
|
||||||
|
FROM racers r
|
||||||
|
JOIN groups g ON r.group_id = g.id
|
||||||
|
ORDER BY r.last_name, r.first_name
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query racers: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var racers []models.Racer
|
||||||
|
for rows.Next() {
|
||||||
|
var r models.Racer
|
||||||
|
var createdAt string
|
||||||
|
if err := rows.Scan(
|
||||||
|
&r.ID, &r.FirstName, &r.LastName, &r.CarNumber, &r.CarWeight,
|
||||||
|
&r.GroupID, &r.GroupName, &createdAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan racer row: %w", err)
|
||||||
|
}
|
||||||
|
r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||||
|
racers = append(racers, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error iterating racer rows: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return racers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRacersByGroup returns racers in a specific group
|
||||||
|
func (db *DB) GetRacersByGroup(groupID int64) ([]models.Racer, error) {
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT r.id, r.first_name, r.last_name, r.car_number, r.car_weight,
|
||||||
|
r.group_id, g.name as group_name, r.created_at
|
||||||
|
FROM racers r
|
||||||
|
JOIN groups g ON r.group_id = g.id
|
||||||
|
WHERE r.group_id = ?
|
||||||
|
ORDER BY r.last_name, r.first_name
|
||||||
|
`, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query racers by group: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var racers []models.Racer
|
||||||
|
for rows.Next() {
|
||||||
|
var r models.Racer
|
||||||
|
var createdAt string
|
||||||
|
if err := rows.Scan(
|
||||||
|
&r.ID, &r.FirstName, &r.LastName, &r.CarNumber, &r.CarWeight,
|
||||||
|
&r.GroupID, &r.GroupName, &createdAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan racer row: %w", err)
|
||||||
|
}
|
||||||
|
r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||||
|
racers = append(racers, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error iterating racer rows: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return racers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRacer returns a racer by ID
|
||||||
|
func (db *DB) GetRacer(id int64) (*models.Racer, error) {
|
||||||
|
var r models.Racer
|
||||||
|
var createdAt string
|
||||||
|
err := db.QueryRow(`
|
||||||
|
SELECT r.id, r.first_name, r.last_name, r.car_number, r.car_weight,
|
||||||
|
r.group_id, g.name as group_name, r.created_at
|
||||||
|
FROM racers r
|
||||||
|
JOIN groups g ON r.group_id = g.id
|
||||||
|
WHERE r.id = ?
|
||||||
|
`, id).Scan(
|
||||||
|
&r.ID, &r.FirstName, &r.LastName, &r.CarNumber, &r.CarWeight,
|
||||||
|
&r.GroupID, &r.GroupName, &createdAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to query racer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRacer updates a racer
|
||||||
|
func (db *DB) UpdateRacer(id int64, firstName, lastName, carNumber string, carWeight float64, groupID int64) error {
|
||||||
|
_, err := db.Exec(
|
||||||
|
"UPDATE racers SET first_name = ?, last_name = ?, car_number = ?, car_weight = ?, group_id = ? WHERE id = ?",
|
||||||
|
firstName, lastName, carNumber, carWeight, groupID, id,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update racer: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRacer deletes a racer
|
||||||
|
func (db *DB) DeleteRacer(id int64) error {
|
||||||
|
_, err := db.Exec("DELETE FROM racers WHERE id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete racer: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB represents the database connection
|
||||||
|
type DB struct {
|
||||||
|
*sql.DB
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new database connection
|
||||||
|
func New(dbPath string) (*DB, error) {
|
||||||
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create database directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open database connection
|
||||||
|
sqlDB, err := sql.Open("sqlite3", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create database instance
|
||||||
|
db := &DB{
|
||||||
|
DB: sqlDB,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize schema
|
||||||
|
if err := db.initSchema(); err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, fmt.Errorf("failed to initialize schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initSchema creates the database tables if they don't exist
|
||||||
|
func (db *DB) initSchema() error {
|
||||||
|
// Create groups table
|
||||||
|
_, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS groups (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create groups table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create racers table
|
||||||
|
_, err = db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS racers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
first_name TEXT NOT NULL,
|
||||||
|
last_name TEXT NOT NULL,
|
||||||
|
car_number TEXT NOT NULL UNIQUE,
|
||||||
|
car_weight REAL NOT NULL,
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (group_id) REFERENCES groups(id)
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create racers table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create races table
|
||||||
|
_, err = db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS races (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create races table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create race_results table
|
||||||
|
_, err = db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS race_results (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
race_id INTEGER NOT NULL,
|
||||||
|
racer_id INTEGER NOT NULL,
|
||||||
|
lane INTEGER NOT NULL,
|
||||||
|
time REAL,
|
||||||
|
place INTEGER,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (race_id) REFERENCES races(id),
|
||||||
|
FOREIGN KEY (racer_id) REFERENCES racers(id),
|
||||||
|
UNIQUE(race_id, lane)
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create race_results table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Group represents a racer group (e.g., age group, division)
|
||||||
|
type Group struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Racer represents a derby racer
|
||||||
|
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"`
|
||||||
|
GroupName string `json:"group_name,omitempty"` // For display purposes
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Race represents a derby race event
|
||||||
|
type Race struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"` // pending, running, completed
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RaceResult represents the result of a racer in a race
|
||||||
|
type RaceResult struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
RaceID int64 `json:"race_id"`
|
||||||
|
RacerID int64 `json:"racer_id"`
|
||||||
|
Lane int `json:"lane"`
|
||||||
|
Time *float64 `json:"time,omitempty"`
|
||||||
|
Place *int `json:"place,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
@ -0,0 +1,358 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"track-gopher/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ Admin(groups []models.Group, racers []models.Racer) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Derby Race Admin</title>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="/static/js/htmx.min.js"></script>
|
||||||
|
<!-- Bootstrap JS Bundle with Popper -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<div class="container py-4">
|
||||||
|
<header class="mb-4">
|
||||||
|
<h1 class="text-center">Derby Race Admin</h1>
|
||||||
|
<nav class="nav nav-pills nav-fill mt-3">
|
||||||
|
<a class="nav-link active" data-bs-toggle="tab" href="#groups">Groups</a>
|
||||||
|
<a class="nav-link" data-bs-toggle="tab" href="#racers">Racers</a>
|
||||||
|
<a class="nav-link" href="/">Race Timer</a>
|
||||||
|
<a class="nav-link" href="/register">Racer Registration</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane fade show active" id="groups">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Groups</h5>
|
||||||
|
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addGroupModal">
|
||||||
|
Add Group
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
for _, group := range groups {
|
||||||
|
<tr>
|
||||||
|
<td>{ group.Name }</td>
|
||||||
|
<td>{ group.Description }</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#editGroupModal"
|
||||||
|
data-id={ fmt.Sprint(group.ID) }
|
||||||
|
data-name={ group.Name }
|
||||||
|
data-description={ group.Description }>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#deleteGroupModal"
|
||||||
|
data-id={ fmt.Sprint(group.ID) }
|
||||||
|
data-name={ group.Name }>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="racers">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Racers</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Car #</th>
|
||||||
|
<th>Weight</th>
|
||||||
|
<th>Group</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
for _, racer := range racers {
|
||||||
|
<tr>
|
||||||
|
<td>{ racer.FirstName } { racer.LastName }</td>
|
||||||
|
<td>{ racer.CarNumber }</td>
|
||||||
|
<td>{ fmt.Sprintf("%.1f", racer.CarWeight) } oz</td>
|
||||||
|
<td>{ racer.GroupName }</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#editRacerModal"
|
||||||
|
data-id={ fmt.Sprint(racer.ID) }
|
||||||
|
data-firstname={ racer.FirstName }
|
||||||
|
data-lastname={ racer.LastName }
|
||||||
|
data-carnumber={ racer.CarNumber }
|
||||||
|
data-carweight={ fmt.Sprintf("%.1f", racer.CarWeight) }
|
||||||
|
data-groupid={ fmt.Sprint(racer.GroupID) }>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#deleteRacerModal"
|
||||||
|
data-id={ fmt.Sprint(racer.ID) }
|
||||||
|
data-name={ racer.FirstName + " " + racer.LastName }>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Group Modal -->
|
||||||
|
<div class="modal fade" id="addGroupModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add Group</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form hx-post="/api/groups" hx-swap="none" hx-on::after-request="location.reload()">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="groupName" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="groupName" name="name" required/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="groupDescription" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="groupDescription" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Group Modal -->
|
||||||
|
<div class="modal fade" id="editGroupModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Group</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form id="editGroupForm" hx-put="/api/groups/0" hx-swap="none" hx-on::after-request="location.reload()">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editGroupName" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="editGroupName" name="name" required/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editGroupDescription" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="editGroupDescription" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Group Modal -->
|
||||||
|
<div class="modal fade" id="deleteGroupModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Delete Group</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete the group "<span id="deleteGroupName"></span>"?</p>
|
||||||
|
<p class="text-danger">This will also delete all racers in this group!</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" id="confirmDeleteGroup" class="btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Racer Modal -->
|
||||||
|
<div class="modal fade" id="editRacerModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Racer</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form id="editRacerForm" hx-put="/api/racers/0" hx-swap="none" hx-on::after-request="location.reload()">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="editFirstName" class="form-label">First Name</label>
|
||||||
|
<input type="text" class="form-control" id="editFirstName" name="first_name" required/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="editLastName" class="form-label">Last Name</label>
|
||||||
|
<input type="text" class="form-control" id="editLastName" name="last_name" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="editCarNumber" class="form-label">Car Number</label>
|
||||||
|
<input type="text" class="form-control" id="editCarNumber" name="car_number" required/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="editCarWeight" class="form-label">Car Weight (oz)</label>
|
||||||
|
<input type="number" step="0.1" class="form-control" id="editCarWeight" name="car_weight" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editGroupID" class="form-label">Group</label>
|
||||||
|
<select class="form-select" id="editGroupID" name="group_id" required>
|
||||||
|
for _, group := range groups {
|
||||||
|
<option value={ fmt.Sprint(group.ID) }>{ group.Name }</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Racer Modal -->
|
||||||
|
<div class="modal fade" id="deleteRacerModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Delete Racer</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete the racer "<span id="deleteRacerName"></span>"?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" id="confirmDeleteRacer" class="btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Handle edit group modal
|
||||||
|
document.getElementById('editGroupModal').addEventListener('show.bs.modal', function (event) {
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
const id = button.getAttribute('data-id');
|
||||||
|
const name = button.getAttribute('data-name');
|
||||||
|
const description = button.getAttribute('data-description');
|
||||||
|
|
||||||
|
const form = document.getElementById('editGroupForm');
|
||||||
|
form.setAttribute('hx-put', `/api/groups/${id}`);
|
||||||
|
|
||||||
|
document.getElementById('editGroupName').value = name;
|
||||||
|
document.getElementById('editGroupDescription').value = description;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle delete group modal
|
||||||
|
document.getElementById('deleteGroupModal').addEventListener('show.bs.modal', function (event) {
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
const id = button.getAttribute('data-id');
|
||||||
|
const name = button.getAttribute('data-name');
|
||||||
|
|
||||||
|
document.getElementById('deleteGroupName').textContent = name;
|
||||||
|
|
||||||
|
document.getElementById('confirmDeleteGroup').onclick = function() {
|
||||||
|
fetch(`/api/groups/${id}`, { method: 'DELETE' })
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Failed to delete group');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle edit racer modal
|
||||||
|
document.getElementById('editRacerModal').addEventListener('show.bs.modal', function (event) {
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
const id = button.getAttribute('data-id');
|
||||||
|
const firstName = button.getAttribute('data-firstname');
|
||||||
|
const lastName = button.getAttribute('data-lastname');
|
||||||
|
const carNumber = button.getAttribute('data-carnumber');
|
||||||
|
const carWeight = button.getAttribute('data-carweight');
|
||||||
|
const groupId = button.getAttribute('data-groupid');
|
||||||
|
|
||||||
|
const form = document.getElementById('editRacerForm');
|
||||||
|
form.setAttribute('hx-put', `/api/racers/${id}`);
|
||||||
|
|
||||||
|
document.getElementById('editFirstName').value = firstName;
|
||||||
|
document.getElementById('editLastName').value = lastName;
|
||||||
|
document.getElementById('editCarNumber').value = carNumber;
|
||||||
|
document.getElementById('editCarWeight').value = carWeight;
|
||||||
|
document.getElementById('editGroupID').value = groupId;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle delete racer modal
|
||||||
|
document.getElementById('deleteRacerModal').addEventListener('show.bs.modal', function (event) {
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
const id = button.getAttribute('data-id');
|
||||||
|
const name = button.getAttribute('data-name');
|
||||||
|
|
||||||
|
document.getElementById('deleteRacerName').textContent = name;
|
||||||
|
|
||||||
|
document.getElementById('confirmDeleteRacer').onclick = function() {
|
||||||
|
fetch(`/api/racers/${id}`, { method: 'DELETE' })
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Failed to delete racer');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
@ -0,0 +1,361 @@
|
|||||||
|
// 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"
|
||||||
|
"track-gopher/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Admin(groups []models.Group, 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_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>Derby Race Admin</title><!-- Bootstrap CSS --><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script><!-- Bootstrap JS Bundle with Popper --><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\"></script></head><body class=\"bg-light\"><div class=\"container py-4\"><header class=\"mb-4\"><h1 class=\"text-center\">Derby Race Admin</h1><nav class=\"nav nav-pills nav-fill mt-3\"><a class=\"nav-link active\" data-bs-toggle=\"tab\" href=\"#groups\">Groups</a> <a class=\"nav-link\" data-bs-toggle=\"tab\" href=\"#racers\">Racers</a> <a class=\"nav-link\" href=\"/\">Race Timer</a> <a class=\"nav-link\" href=\"/register\">Racer Registration</a></nav></header><div class=\"tab-content\"><div class=\"tab-pane fade show active\" id=\"groups\"><div class=\"card mb-4\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\">Groups</h5><button class=\"btn btn-primary btn-sm\" data-bs-toggle=\"modal\" data-bs-target=\"#addGroupModal\">Add Group</button></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-striped\"><thead><tr><th>Name</th><th>Description</th><th>Actions</th></tr></thead> <tbody>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, group := range groups {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<tr><td>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 55, Col: 28}
|
||||||
|
}
|
||||||
|
_, 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, 3, "</td><td>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(group.Description)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 56, Col: 35}
|
||||||
|
}
|
||||||
|
_, 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, 4, "</td><td><button class=\"btn btn-sm btn-outline-primary me-1\" data-bs-toggle=\"modal\" data-bs-target=\"#editGroupModal\" data-id=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(group.ID))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 61, Col: 44}
|
||||||
|
}
|
||||||
|
_, 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, 5, "\" data-name=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 62, Col: 36}
|
||||||
|
}
|
||||||
|
_, 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, 6, "\" data-description=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(group.Description)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 63, Col: 50}
|
||||||
|
}
|
||||||
|
_, 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, 7, "\">Edit</button> <button class=\"btn btn-sm btn-outline-danger\" data-bs-toggle=\"modal\" data-bs-target=\"#deleteGroupModal\" data-id=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(group.ID))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 69, Col: 44}
|
||||||
|
}
|
||||||
|
_, 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, 8, "\" data-name=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 70, Col: 36}
|
||||||
|
}
|
||||||
|
_, 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, 9, "\">Delete</button></td></tr>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</tbody></table></div></div></div></div><div class=\"tab-pane fade\" id=\"racers\"><div class=\"card mb-4\"><div class=\"card-header\"><h5 class=\"mb-0\">Racers</h5></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-striped\"><thead><tr><th>Name</th><th>Car #</th><th>Weight</th><th>Group</th><th>Actions</th></tr></thead> <tbody>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, racer := range racers {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<tr><td>")
|
||||||
|
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/admin.templ`, Line: 103, Col: 33}
|
||||||
|
}
|
||||||
|
_, 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, 12, " ")
|
||||||
|
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/admin.templ`, Line: 103, Col: 52}
|
||||||
|
}
|
||||||
|
_, 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, 13, "</td><td>")
|
||||||
|
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/admin.templ`, Line: 104, Col: 33}
|
||||||
|
}
|
||||||
|
_, 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, 14, "</td><td>")
|
||||||
|
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", racer.CarWeight))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 105, Col: 54}
|
||||||
|
}
|
||||||
|
_, 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, 15, " oz</td><td>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var13 string
|
||||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(racer.GroupName)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 106, Col: 33}
|
||||||
|
}
|
||||||
|
_, 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, 16, "</td><td><button class=\"btn btn-sm btn-outline-primary me-1\" data-bs-toggle=\"modal\" data-bs-target=\"#editRacerModal\" data-id=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var14 string
|
||||||
|
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(racer.ID))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 111, Col: 44}
|
||||||
|
}
|
||||||
|
_, 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, 17, "\" data-firstname=\"")
|
||||||
|
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/admin.templ`, Line: 112, Col: 46}
|
||||||
|
}
|
||||||
|
_, 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, 18, "\" data-lastname=\"")
|
||||||
|
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/admin.templ`, Line: 113, Col: 44}
|
||||||
|
}
|
||||||
|
_, 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, 19, "\" data-carnumber=\"")
|
||||||
|
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/admin.templ`, Line: 114, Col: 46}
|
||||||
|
}
|
||||||
|
_, 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, 20, "\" data-carweight=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var18 string
|
||||||
|
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", racer.CarWeight))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 115, Col: 67}
|
||||||
|
}
|
||||||
|
_, 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, 21, "\" data-groupid=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var19 string
|
||||||
|
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(racer.GroupID))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 116, Col: 54}
|
||||||
|
}
|
||||||
|
_, 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, 22, "\">Edit</button> <button class=\"btn btn-sm btn-outline-danger\" data-bs-toggle=\"modal\" data-bs-target=\"#deleteRacerModal\" data-id=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var20 string
|
||||||
|
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(racer.ID))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 122, Col: 44}
|
||||||
|
}
|
||||||
|
_, 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, 23, "\" data-name=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var21 string
|
||||||
|
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName + " " + racer.LastName)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 123, Col: 64}
|
||||||
|
}
|
||||||
|
_, 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, 24, "\">Delete</button></td></tr>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</tbody></table></div></div></div></div></div></div><!-- Add Group Modal --><div class=\"modal fade\" id=\"addGroupModal\" tabindex=\"-1\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\">Add Group</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form hx-post=\"/api/groups\" hx-swap=\"none\" hx-on::after-request=\"location.reload()\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"groupName\" class=\"form-label\">Name</label> <input type=\"text\" class=\"form-control\" id=\"groupName\" name=\"name\" required></div><div class=\"mb-3\"><label for=\"groupDescription\" class=\"form-label\">Description</label> <textarea class=\"form-control\" id=\"groupDescription\" name=\"description\" rows=\"3\"></textarea></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save</button></div></form></div></div></div><!-- Edit Group Modal --><div class=\"modal fade\" id=\"editGroupModal\" tabindex=\"-1\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\">Edit Group</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"editGroupForm\" hx-put=\"/api/groups/0\" hx-swap=\"none\" hx-on::after-request=\"location.reload()\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"editGroupName\" class=\"form-label\">Name</label> <input type=\"text\" class=\"form-control\" id=\"editGroupName\" name=\"name\" required></div><div class=\"mb-3\"><label for=\"editGroupDescription\" class=\"form-label\">Description</label> <textarea class=\"form-control\" id=\"editGroupDescription\" name=\"description\" rows=\"3\"></textarea></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save</button></div></form></div></div></div><!-- Delete Group Modal --><div class=\"modal fade\" id=\"deleteGroupModal\" tabindex=\"-1\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\">Delete Group</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><p>Are you sure you want to delete the group \"<span id=\"deleteGroupName\"></span>\"?</p><p class=\"text-danger\">This will also delete all racers in this group!</p></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" id=\"confirmDeleteGroup\" class=\"btn btn-danger\">Delete</button></div></div></div></div><!-- Edit Racer Modal --><div class=\"modal fade\" id=\"editRacerModal\" tabindex=\"-1\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\">Edit Racer</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"editRacerForm\" hx-put=\"/api/racers/0\" hx-swap=\"none\" hx-on::after-request=\"location.reload()\"><div class=\"modal-body\"><div class=\"row\"><div class=\"col-md-6 mb-3\"><label for=\"editFirstName\" class=\"form-label\">First Name</label> <input type=\"text\" class=\"form-control\" id=\"editFirstName\" name=\"first_name\" required></div><div class=\"col-md-6 mb-3\"><label for=\"editLastName\" class=\"form-label\">Last Name</label> <input type=\"text\" class=\"form-control\" id=\"editLastName\" name=\"last_name\" required></div></div><div class=\"row\"><div class=\"col-md-6 mb-3\"><label for=\"editCarNumber\" class=\"form-label\">Car Number</label> <input type=\"text\" class=\"form-control\" id=\"editCarNumber\" name=\"car_number\" required></div><div class=\"col-md-6 mb-3\"><label for=\"editCarWeight\" class=\"form-label\">Car Weight (oz)</label> <input type=\"number\" step=\"0.1\" class=\"form-control\" id=\"editCarWeight\" name=\"car_weight\" required></div></div><div class=\"mb-3\"><label for=\"editGroupID\" class=\"form-label\">Group</label> <select class=\"form-select\" id=\"editGroupID\" name=\"group_id\" required>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, group := range groups {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<option value=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var22 string
|
||||||
|
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(group.ID))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 248, Col: 46}
|
||||||
|
}
|
||||||
|
_, 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, 27, "\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var23 string
|
||||||
|
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 248, Col: 61}
|
||||||
|
}
|
||||||
|
_, 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, 28, "</option>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</select></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save</button></div></form></div></div></div><!-- Delete Racer Modal --><div class=\"modal fade\" id=\"deleteRacerModal\" tabindex=\"-1\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\">Delete Racer</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><p>Are you sure you want to delete the racer \"<span id=\"deleteRacerName\"></span>\"?</p></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" id=\"confirmDeleteRacer\" class=\"btn btn-danger\">Delete</button></div></div></div></div><script>\r\n\t\t\t// Handle edit group modal\r\n\t\t\tdocument.getElementById('editGroupModal').addEventListener('show.bs.modal', function (event) {\r\n\t\t\t\tconst button = event.relatedTarget;\r\n\t\t\t\tconst id = button.getAttribute('data-id');\r\n\t\t\t\tconst name = button.getAttribute('data-name');\r\n\t\t\t\tconst description = button.getAttribute('data-description');\r\n\t\t\t\t\r\n\t\t\t\tconst form = document.getElementById('editGroupForm');\r\n\t\t\t\tform.setAttribute('hx-put', `/api/groups/${id}`);\r\n\t\t\t\t\r\n\t\t\t\tdocument.getElementById('editGroupName').value = name;\r\n\t\t\t\tdocument.getElementById('editGroupDescription').value = description;\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Handle delete group modal\r\n\t\t\tdocument.getElementById('deleteGroupModal').addEventListener('show.bs.modal', function (event) {\r\n\t\t\t\tconst button = event.relatedTarget;\r\n\t\t\t\tconst id = button.getAttribute('data-id');\r\n\t\t\t\tconst name = button.getAttribute('data-name');\r\n\t\t\t\t\r\n\t\t\t\tdocument.getElementById('deleteGroupName').textContent = name;\r\n\t\t\t\t\r\n\t\t\t\tdocument.getElementById('confirmDeleteGroup').onclick = function() {\r\n\t\t\t\t\tfetch(`/api/groups/${id}`, { method: 'DELETE' })\r\n\t\t\t\t\t\t.then(response => {\r\n\t\t\t\t\t\t\tif (response.ok) {\r\n\t\t\t\t\t\t\t\tlocation.reload();\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\talert('Failed to delete group');\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t};\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Handle edit racer modal\r\n\t\t\tdocument.getElementById('editRacerModal').addEventListener('show.bs.modal', function (event) {\r\n\t\t\t\tconst button = event.relatedTarget;\r\n\t\t\t\tconst id = button.getAttribute('data-id');\r\n\t\t\t\tconst firstName = button.getAttribute('data-firstname');\r\n\t\t\t\tconst lastName = button.getAttribute('data-lastname');\r\n\t\t\t\tconst carNumber = button.getAttribute('data-carnumber');\r\n\t\t\t\tconst carWeight = button.getAttribute('data-carweight');\r\n\t\t\t\tconst groupId = button.getAttribute('data-groupid');\r\n\t\t\t\t\r\n\t\t\t\tconst form = document.getElementById('editRacerForm');\r\n\t\t\t\tform.setAttribute('hx-put', `/api/racers/${id}`);\r\n\t\t\t\t\r\n\t\t\t\tdocument.getElementById('editFirstName').value = firstName;\r\n\t\t\t\tdocument.getElementById('editLastName').value = lastName;\r\n\t\t\t\tdocument.getElementById('editCarNumber').value = carNumber;\r\n\t\t\t\tdocument.getElementById('editCarWeight').value = carWeight;\r\n\t\t\t\tdocument.getElementById('editGroupID').value = groupId;\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Handle delete racer modal\r\n\t\t\tdocument.getElementById('deleteRacerModal').addEventListener('show.bs.modal', function (event) {\r\n\t\t\t\tconst button = event.relatedTarget;\r\n\t\t\t\tconst id = button.getAttribute('data-id');\r\n\t\t\t\tconst name = button.getAttribute('data-name');\r\n\t\t\t\t\r\n\t\t\t\tdocument.getElementById('deleteRacerName').textContent = name;\r\n\t\t\t\t\r\n\t\t\t\tdocument.getElementById('confirmDeleteRacer').onclick = function() {\r\n\t\t\t\t\tfetch(`/api/racers/${id}`, { method: 'DELETE' })\r\n\t\t\t\t\t\t.then(response => {\r\n\t\t\t\t\t\t\tif (response.ok) {\r\n\t\t\t\t\t\t\t\tlocation.reload();\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\talert('Failed to delete racer');\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t};\r\n\t\t\t});\r\n\t\t</script></body></html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"track-gopher/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ Register(groups []models.Group) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Racer Registration</title>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="/static/js/htmx.min.js"></script>
|
||||||
|
<!-- Bootstrap JS Bundle with Popper -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<div class="container py-4">
|
||||||
|
<header class="mb-4">
|
||||||
|
<h1 class="text-center">Racer Registration</h1>
|
||||||
|
<div class="d-flex justify-content-center mt-3">
|
||||||
|
<a href="/" class="btn btn-outline-primary me-2">Race Timer</a>
|
||||||
|
<a href="/admin" class="btn btn-outline-secondary">Admin</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8 col-lg-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Register New Racer</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="registerForm" hx-post="/api/racers" hx-swap="none">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="firstName" class="form-label">First Name</label>
|
||||||
|
<input type="text" class="form-control" id="firstName" name="first_name" required/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="lastName" class="form-label">Last Name</label>
|
||||||
|
<input type="text" class="form-control" id="lastName" name="last_name" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="carNumber" class="form-label">Car Number</label>
|
||||||
|
<input type="text" class="form-control" id="carNumber" name="car_number" required/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="carWeight" class="form-label">Car Weight (oz)</label>
|
||||||
|
<input type="number" step="0.1" class="form-control" id="carWeight" name="car_weight" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="groupID" class="form-label">Group</label>
|
||||||
|
<select class="form-select" id="groupID" name="group_id" required>
|
||||||
|
<option value="" selected disabled>Select a group</option>
|
||||||
|
for _, group := range groups {
|
||||||
|
<option value={ fmt.Sprint(group.ID) }>{ group.Name }</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary">Register Racer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="successAlert" class="alert alert-success mt-3 d-none">
|
||||||
|
<h4 class="alert-heading">Registration Successful!</h4>
|
||||||
|
<p>The racer has been registered successfully.</p>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button class="btn btn-outline-success" onclick="resetForm()">Register Another Racer</button>
|
||||||
|
<a href="/admin" class="btn btn-outline-primary">View All Racers</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="errorAlert" class="alert alert-danger mt-3 d-none">
|
||||||
|
<h4 class="alert-heading">Registration Failed</h4>
|
||||||
|
<p id="errorMessage">There was an error registering the racer.</p>
|
||||||
|
<hr>
|
||||||
|
<button class="btn btn-outline-danger" onclick="dismissError()">Dismiss</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('registerForm').addEventListener('htmx:afterRequest', function(event) {
|
||||||
|
const response = event.detail.xhr;
|
||||||
|
|
||||||
|
if (response.status === 200 || response.status === 201) {
|
||||||
|
// Show success message
|
||||||
|
document.getElementById('successAlert').classList.remove('d-none');
|
||||||
|
document.getElementById('errorAlert').classList.add('d-none');
|
||||||
|
} else {
|
||||||
|
// Show error message
|
||||||
|
document.getElementById('errorAlert').classList.remove('d-none');
|
||||||
|
document.getElementById('successAlert').classList.add('d-none');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const error = JSON.parse(response.responseText);
|
||||||
|
document.getElementById('errorMessage').textContent = error.message || 'There was an error registering the racer.';
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('errorMessage').textContent = 'There was an error registering the racer.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
document.getElementById('registerForm').reset();
|
||||||
|
document.getElementById('successAlert').classList.add('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissError() {
|
||||||
|
document.getElementById('errorAlert').classList.add('d-none');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
// 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"
|
||||||
|
"track-gopher/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(groups []models.Group) 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>Racer Registration</title><!-- Bootstrap CSS --><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script><!-- Bootstrap JS Bundle with Popper --><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\"></script></head><body class=\"bg-light\"><div class=\"container py-4\"><header class=\"mb-4\"><h1 class=\"text-center\">Racer Registration</h1><div class=\"d-flex justify-content-center mt-3\"><a href=\"/\" class=\"btn btn-outline-primary me-2\">Race Timer</a> <a href=\"/admin\" class=\"btn btn-outline-secondary\">Admin</a></div></header><div class=\"row justify-content-center\"><div class=\"col-md-8 col-lg-6\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Register New Racer</h5></div><div class=\"card-body\"><form id=\"registerForm\" hx-post=\"/api/racers\" hx-swap=\"none\"><div class=\"row\"><div class=\"col-md-6 mb-3\"><label for=\"firstName\" class=\"form-label\">First Name</label> <input type=\"text\" class=\"form-control\" id=\"firstName\" name=\"first_name\" required></div><div class=\"col-md-6 mb-3\"><label for=\"lastName\" class=\"form-label\">Last Name</label> <input type=\"text\" class=\"form-control\" id=\"lastName\" name=\"last_name\" required></div></div><div class=\"row\"><div class=\"col-md-6 mb-3\"><label for=\"carNumber\" class=\"form-label\">Car Number</label> <input type=\"text\" class=\"form-control\" id=\"carNumber\" name=\"car_number\" required></div><div class=\"col-md-6 mb-3\"><label for=\"carWeight\" class=\"form-label\">Car Weight (oz)</label> <input type=\"number\" step=\"0.1\" class=\"form-control\" id=\"carWeight\" name=\"car_weight\" required></div></div><div class=\"mb-3\"><label for=\"groupID\" class=\"form-label\">Group</label> <select class=\"form-select\" id=\"groupID\" name=\"group_id\" required><option value=\"\" selected disabled>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_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(group.ID))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 64, Col: 47}
|
||||||
|
}
|
||||||
|
_, 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, 3, "\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 64, Col: 62}
|
||||||
|
}
|
||||||
|
_, 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, 4, "</option>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</select></div><div class=\"d-grid\"><button type=\"submit\" class=\"btn btn-primary\">Register Racer</button></div></form></div></div><div id=\"successAlert\" class=\"alert alert-success mt-3 d-none\"><h4 class=\"alert-heading\">Registration Successful!</h4><p>The racer has been registered successfully.</p><hr><div class=\"d-flex justify-content-between\"><button class=\"btn btn-outline-success\" onclick=\"resetForm()\">Register Another Racer</button> <a href=\"/admin\" class=\"btn btn-outline-primary\">View All Racers</a></div></div><div id=\"errorAlert\" class=\"alert alert-danger mt-3 d-none\"><h4 class=\"alert-heading\">Registration Failed</h4><p id=\"errorMessage\">There was an error registering the racer.</p><hr><button class=\"btn btn-outline-danger\" onclick=\"dismissError()\">Dismiss</button></div></div></div></div><script>\r\n\t\t\tdocument.getElementById('registerForm').addEventListener('htmx:afterRequest', function(event) {\r\n\t\t\t\tconst response = event.detail.xhr;\r\n\t\t\t\t\r\n\t\t\t\tif (response.status === 200 || response.status === 201) {\r\n\t\t\t\t\t// Show success message\r\n\t\t\t\t\tdocument.getElementById('successAlert').classList.remove('d-none');\r\n\t\t\t\t\tdocument.getElementById('errorAlert').classList.add('d-none');\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// Show error message\r\n\t\t\t\t\tdocument.getElementById('errorAlert').classList.remove('d-none');\r\n\t\t\t\t\tdocument.getElementById('successAlert').classList.add('d-none');\r\n\t\t\t\t\t\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tconst error = JSON.parse(response.responseText);\r\n\t\t\t\t\t\tdocument.getElementById('errorMessage').textContent = error.message || 'There was an error registering the racer.';\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tdocument.getElementById('errorMessage').textContent = 'There was an error registering the racer.';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tfunction resetForm() {\r\n\t\t\t\tdocument.getElementById('registerForm').reset();\r\n\t\t\t\tdocument.getElementById('successAlert').classList.add('d-none');\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfunction dismissError() {\r\n\t\t\t\tdocument.getElementById('errorAlert').classList.add('d-none');\r\n\t\t\t}\r\n\t\t</script></body></html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
Loading…
Reference in new issue