You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
10 KiB
224 lines
10 KiB
package templates
|
|
|
|
import (
|
|
"track-gopher/models"
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
// RaceManage renders the race management view
|
|
templ RaceManage(heatData *models.HeatData, nextHeat *models.HeatData, groups []models.Group, results []models.HeatResult) {
|
|
@Layout("Race Management") {
|
|
<div class="container-fluid mt-3">
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between">
|
|
<h2>Race Management</h2>
|
|
<div>
|
|
<button id="reveal-btn" class="btn btn-warning"
|
|
hx-post="/api/results/reveal"
|
|
hx-swap="none">
|
|
<i class="bi bi-eye"></i> Reveal
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header bg-primary text-white">
|
|
<h4 class="mb-0">Race Control</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label for="group-select" class="form-label">Racing Group</label>
|
|
<select id="group-select" class="form-select" name="group_id" hx-post="/api/race/set-group" hx-trigger="change" hx-swap="none">
|
|
for _, group := range groups {
|
|
<option value={ strconv.FormatInt(group.ID, 10) } selected?={ group.ID == heatData.Group.ID }>
|
|
{ group.Name }
|
|
</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h5 hx-ext="sse" sse-connect="/api/admin/events" sse-swap="heat-number">{ heatData.Group.Name } - Heat: { strconv.Itoa(heatData.HeatNumber) } of { strconv.Itoa(heatData.TotalHeats) }</h5>
|
|
<div class="btn-group" role="group">
|
|
<button id="prev-heat-btn" class="btn btn-secondary" hx-post="/api/race/previous-heat" hx-swap="none" disabled?={ heatData.HeatNumber <= 1 }>
|
|
<i class="bi bi-arrow-left"></i> Previous Heat
|
|
</button>
|
|
<button id="next-heat-btn" class="btn btn-secondary" hx-post="/api/race/next-heat" hx-swap="none" disabled?={ heatData.HeatNumber >= heatData.TotalHeats }>
|
|
Next Heat <i class="bi bi-arrow-right"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h5>Timer Control</h5>
|
|
<div class="d-flex align-items-center mb-2" hx-ext="sse" sse-connect="/api/events" sse-swap="race-status">
|
|
<div id="status-indicator" class="w-25 h-100 d-inline-flex align-items-center justify-content-center badge bg-primary">Idle</div>
|
|
</div>
|
|
<div class="btn-group" role="group">
|
|
<button class="btn btn-warning" hx-post="/api/reset" hx-swap="none">
|
|
<i class="bi bi-arrow-repeat"></i> Reset Timer
|
|
</button>
|
|
<button class="btn btn-danger" hx-post="/api/force-end" hx-swap="none">
|
|
<i class="bi bi-flag-fill"></i> Force End
|
|
</button>
|
|
<button class="btn btn-info" hx-post="/api/race/rerun-heat" hx-swap="none">
|
|
<i class="bi bi-arrow-counterclockwise"></i> Re-Run Heat
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="gate-status" class="alert alert-secondary">
|
|
<strong>Gate Status:</strong> <span id="gate-status-text">Unknown</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6" hx-ext="sse" sse-connect="/api/admin/events" sse-swap="results">
|
|
@ResultsDisplay(results)
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current Heat Display -->
|
|
<div hx-ext="sse" sse-connect="/api/admin/events" sse-swap="current-heat">
|
|
@CurrentHeatDisplay(heatData)
|
|
</div>
|
|
|
|
<!-- Next Heat Preview -->
|
|
<div hx-ext="sse" sse-connect="/api/admin/events" sse-swap="next-heat">
|
|
@NextHeatDisplay(nextHeat)
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
templ ResultsDisplay(results []models.HeatResult) {
|
|
<div class="card">
|
|
<div class="card-header bg-primary text-white">
|
|
<h4 class="mb-0">Heat Results</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Heat</th>
|
|
<th>Lane 1</th>
|
|
<th>Lane 2</th>
|
|
<th>Lane 3</th>
|
|
<th>Lane 4</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
for _, result := range results {
|
|
<tr class={ "table-primary" }>
|
|
<td>{ strconv.Itoa(result.HeatNumber) }</td>
|
|
<td>{ fmt.Sprintf("%.3f", result.Lane1Time) } ({ strconv.Itoa(result.Lane1Position) })</td>
|
|
<td>{ fmt.Sprintf("%.3f", result.Lane2Time) } ({ strconv.Itoa(result.Lane2Position) })</td>
|
|
<td>{ fmt.Sprintf("%.3f", result.Lane3Time) } ({ strconv.Itoa(result.Lane3Position) })</td>
|
|
<td>{ fmt.Sprintf("%.3f", result.Lane4Time) } ({ strconv.Itoa(result.Lane4Position) })</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
// Find the current heat
|
|
templ CurrentHeatDisplay(heatData *models.HeatData) {
|
|
<div class="card mb-4">
|
|
<div class="card-header bg-primary text-white">
|
|
<h4 class="mb-0">Current Heat: { strconv.Itoa(heatData.HeatNumber) }</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
if heatData.Lane1 != nil {
|
|
@raceLaneInfo(*heatData.Lane1)
|
|
}
|
|
if heatData.Lane2 != nil {
|
|
@raceLaneInfo(*heatData.Lane2)
|
|
}
|
|
if heatData.Lane3 != nil {
|
|
@raceLaneInfo(*heatData.Lane3)
|
|
}
|
|
if heatData.Lane4 != nil {
|
|
@raceLaneInfo(*heatData.Lane4)
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
// Helper template for displaying a lane in the race manager
|
|
templ raceLaneInfo(laneData models.LaneData) {
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card h-100">
|
|
<div class="card-header bg-secondary text-white">
|
|
<h5 class="mb-0">Lane { strconv.Itoa(laneData.Lane) }</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<h5 class="card-title">{ laneData.Name }</h5>
|
|
<p class="card-text">Car #: { laneData.CarNum }</p>
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<strong>Time:</strong> <span hx-ext="sse" sse-connect="/api/events" sse-swap={fmt.Sprintf("lane-%d-time", laneData.Lane)}>{ fmt.Sprintf("%.3f", laneData.Time) }</span>
|
|
</div>
|
|
<div>
|
|
<strong>Position:</strong> <span hx-ext="sse" sse-connect="/api/events" sse-swap={fmt.Sprintf("lane-%d-position", laneData.Lane)}>{ strconv.Itoa(laneData.Place) }</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
templ NextHeatDisplay(nextHeat *models.HeatData) {
|
|
if nextHeat != nil {
|
|
<div class="card mb-4">
|
|
<div class="card-header bg-secondary text-white">
|
|
<h4 class="mb-0">Next Heat: { strconv.Itoa(nextHeat.HeatNumber) }</h4>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Lane</th>
|
|
<th>Racer</th>
|
|
<th>Car #</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
if nextHeat.Lane1 != nil {
|
|
@raceNextHeatRow(*nextHeat.Lane1)
|
|
}
|
|
if nextHeat.Lane2 != nil {
|
|
@raceNextHeatRow(*nextHeat.Lane2)
|
|
}
|
|
if nextHeat.Lane3 != nil {
|
|
@raceNextHeatRow(*nextHeat.Lane3)
|
|
}
|
|
if nextHeat.Lane4 != nil {
|
|
@raceNextHeatRow(*nextHeat.Lane4)
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
// Helper template for displaying a racer in the next heat
|
|
templ nextHeatRacer(laneData models.LaneData) {
|
|
<tr>
|
|
<td>{ strconv.Itoa(laneData.Lane) }</td>
|
|
<td>{ laneData.Name }</td>
|
|
<td>{ laneData.CarNum }</td>
|
|
</tr>
|
|
} |