Lots of changes lets see if they work

main
DustyP 9 months ago
parent b1dccc9552
commit c60539ef76

@ -441,3 +441,115 @@ func (db *DB) DeleteHeatResult(groupID int64, heatNum int) error {
groupID, heatNum)
return err
}
func (db *DB) GetHeatData(groupID int64, heatNum int) (*models.HeatData, error) {
heatData := &models.HeatData{}
lane1 := int64(0)
lane2 := int64(0)
lane3 := int64(0)
lane4 := int64(0)
err := db.QueryRow(`
SELECT
group_id, heat_number,
g.name as group_name,
(SELECT COUNT(*) FROM heats WHERE group_id = ?) as total_heats,
h.lane1_id, h.lane2_id, h.lane3_id, h.lane4_id
FROM heats h
JOIN groups g ON g.id = h.group_id
WHERE h.group_id = ? AND h.heat_num = ?`,
groupID, groupID, heatNum).Scan(
&heatData.Group.ID, &heatData.HeatNumber,
&heatData.Group.Name, &heatData.TotalHeats,
&lane1, &lane2, &lane3, &lane4)
if err != nil {
return nil, fmt.Errorf("error getting heat data: %v", err)
}
// Get racer data for each lane
if lane1 != 0 {
lane1Data, err := db.getLaneData(1, lane1, heatNum, groupID)
if err != nil {
return nil, err
}
heatData.Lane1 = lane1Data
}
if lane2 != 0 {
lane2Data, err := db.getLaneData(2, lane2, heatNum, groupID)
if err != nil {
return nil, err
}
heatData.Lane2 = lane2Data
}
if lane3 != 0 {
lane3Data, err := db.getLaneData(3, lane3, heatNum, groupID)
if err != nil {
return nil, err
}
heatData.Lane3 = lane3Data
}
if lane4 != 0 {
lane4Data, err := db.getLaneData(4, lane4, heatNum, groupID)
if err != nil {
return nil, err
}
heatData.Lane4 = lane4Data
}
return heatData, nil
}
// getLaneData gets the racer data for a specific lane
func (db *DB) getLaneData(lane int, racerID int64, heatNum int, groupID int64) (*models.LaneData, error) {
// Get racer data
var racer models.Racer
err := db.QueryRow(`
SELECT id, first_name, last_name, car_number, car_weight, group_id
FROM racers
WHERE id = ?`, racerID).Scan(
&racer.ID, &racer.FirstName, &racer.LastName, &racer.CarNumber, &racer.CarWeight, &racer.GroupID)
if err != nil {
if err == sql.ErrNoRows {
return nil, ErrNotFound
}
return nil, err
}
// Create lane data with racer info
laneData := &models.LaneData{
Lane: lane,
RacerID: racerID,
Name: racer.FirstName + " " + racer.LastName,
CarNum: racer.CarNumber,
CarWeight: racer.CarWeight,
Time: 0,
Place: 0,
}
heatResult, err := db.GetHeatResult(groupID, heatNum)
if err != nil {
return laneData, nil
}
switch lane {
case 1:
laneData.Time = heatResult.Lane1Time
laneData.Place = heatResult.Lane1Position
case 2:
laneData.Time = heatResult.Lane2Time
laneData.Place = heatResult.Lane2Position
case 3:
laneData.Time = heatResult.Lane3Time
laneData.Place = heatResult.Lane3Position
case 4:
laneData.Time = heatResult.Lane4Time
laneData.Place = heatResult.Lane4Position
}
return laneData, nil
}

@ -36,12 +36,15 @@ const (
EventRaceStart EventType = iota
EventLaneFinish
EventRaceComplete
EventHeatChanged
EventGroupChanged
EventHeatRerun
)
// Event represents a race event
type Event struct {
Type EventType
Result *Result // Only populated for EventLaneFinish
Type EventType
Event any // Only populated for EventLaneFinish
}
// DerbyClock represents the connection to the derby clock device
@ -202,8 +205,8 @@ func (dc *DerbyClock) readLoop() {
if result != nil {
// Send lane finish event
dc.eventChan <- Event{
Type: EventLaneFinish,
Result: result,
Type: EventLaneFinish,
Event: result,
}
}
results = append(results, result)

@ -162,10 +162,10 @@ func startTerminalInterface(clock *derby.DerbyClock, events <-chan derby.Event,
fmt.Println("\n🏁 Race started!")
case derby.EventLaneFinish:
result := event.Result
result := event.Event.(derby.Result)
fmt.Printf("🚗 Lane %d finished in place %d with time %.4f seconds\n",
result.Lane, result.FinishPlace, result.Time)
raceResults = append(raceResults, result)
raceResults = append(raceResults, &result)
case derby.EventRaceComplete:
fmt.Println("\n🏆 Race complete! Final results:")

@ -67,3 +67,23 @@ type HeatResult struct {
Lane4Time float64 `json:"lane4_time"`
Lane4Position int `json:"lane4_position"`
}
type LaneData struct {
Lane int
RacerID int64
Name string
CarNum string
CarWeight float64
Time float64
Place int
}
type HeatData struct {
Group Group
HeatNumber int
TotalHeats int
Lane1 *LaneData
Lane2 *LaneData
Lane3 *LaneData
Lane4 *LaneData
}

@ -148,9 +148,6 @@ func (s *Server) routes() {
// Add racers list route
s.router.Get("/admin/racers/list", s.handleRacersList())
// Add admin events route
s.router.Get("/api/admin-events", s.handleAdminEvents())
// Add validate car number route
s.router.Get("/api/validate/car-number", s.handleValidateCarNumber())
@ -163,7 +160,6 @@ func (s *Server) routes() {
r.Get("/current-heat", s.handleCurrentHeat())
r.Post("/next-heat", s.handleNextHeat())
r.Post("/previous-heat", s.handlePreviousHeat())
r.Post("/save-result", s.handleSaveHeatResult())
r.Post("/rerun-heat", s.handleRerunHeat())
r.Post("/set-group", s.handleSetRacingGroup())
})
@ -229,49 +225,36 @@ func (s *Server) forwardEvents() {
// broadcastEvent sends an event to all connected clients
func (s *Server) broadcastEvent(event derby.Event) {
var message string
switch event.Type {
case derby.EventRaceStart:
s.logger.Info("Broadcasting race start event")
statusMsg := struct {
Status string `json:"status"`
}{
Status: "running",
}
statusJSON, _ := json.Marshal(statusMsg)
message = fmt.Sprintf("event: status\ndata: %s", statusJSON)
s.sendEventToAllClients("event: race-status\ndata: <div id='status-indicator' class='badge bg-success'>Race Running</div>")
case derby.EventLaneFinish:
s.logger.Info("Broadcasting lane finish event",
"lane", event.Result.Lane,
"time", event.Result.Time,
"place", event.Result.FinishPlace)
// Create a message for lane finish
laneData := struct {
Lane int `json:"lane"`
Time float64 `json:"time"`
Place int `json:"place"`
}{
Lane: event.Result.Lane,
Time: event.Result.Time,
Place: event.Result.FinishPlace,
}
laneJSON, _ := json.Marshal(laneData)
message = fmt.Sprintf("event: lane-finish\ndata: %s", laneJSON)
"lane", event.Event.(derby.Result).Lane,
"time", event.Event.(derby.Result).Time,
"place", event.Event.(derby.Result).FinishPlace)
s.sendEventToAllClients(fmt.Sprintf("event: lane-%d-time\ndata: %.4f", event.Event.(derby.Result).Lane, event.Event.(derby.Result).Time))
s.sendEventToAllClients(fmt.Sprintf("event: lane-%d-position\ndata: %d", event.Event.(derby.Result).Lane, event.Event.(derby.Result).FinishPlace))
case derby.EventHeatChanged:
s.logger.Info("Broadcasting heat changed event")
s.sendEventToAllClients(fmt.Sprintf("event: heat-changed-number\ndata: %d out of %d", event.Event.(models.HeatData).HeatNumber, event.Event.(models.HeatData).TotalHeats))
s.sendEventToAllClients("event: race-status\ndata: <div id='status-indicator' class='badge bg-secondary'>Idle</div>")
case derby.EventGroupChanged:
s.logger.Info("Broadcasting group changed event")
s.sendEventToAllClients("event: group-changed\ndata: <div id='status-indicator' class='badge bg-success'>Group Changed</div>")
case derby.EventRaceComplete:
s.logger.Info("Broadcasting race complete event")
statusMsg := struct {
Status string `json:"status"`
}{
Status: "finished",
}
statusJSON, _ := json.Marshal(statusMsg)
message = fmt.Sprintf("event: status\ndata: %s", statusJSON)
s.sendEventToAllClients("event: race-status\ndata: <div id='status-indicator' class='badge bg-info'>Race Complete</div>")
}
}
func (s *Server) sendEventToAllClients(message string) {
if message == "" {
return
}
@ -281,17 +264,13 @@ func (s *Server) broadcastEvent(event derby.Event) {
clientCount := len(s.clients)
sentCount := 0
for clientChan := range s.clients {
select {
case clientChan <- message:
sentCount++
default:
s.logger.Warn("Client channel is full, event not sent")
}
clientChan <- message
sentCount++
}
s.logger.Info("Event broadcast complete",
s.logger.Debug("Event broadcast complete",
"sentCount", sentCount,
"totalClients", clientCount,
"eventType", event.Type)
"message", message)
s.clientsMux.Unlock()
}
@ -1026,31 +1005,21 @@ func (s *Server) handleRacePublic() http.HandlerFunc {
}
// Get heats for the group
heats, err := s.db.GetHeats(currentGroup.ID)
heatData, err := s.db.GetHeatData(currentGroup.ID, currentHeatNum)
if err != nil {
s.logger.Error("Failed to get heats", "error", err)
http.Error(w, "Failed to get heats", http.StatusInternalServerError)
return
}
// Get racers for the group
racers, err := s.db.GetRacersByGroup(currentGroup.ID)
if err != nil {
s.logger.Error("Failed to get racers", "error", err)
http.Error(w, "Failed to get racers", http.StatusInternalServerError)
return
}
// Get next heat data
nextHeatData, _ := s.db.GetHeatData(currentGroup.ID, currentHeatNum+1)
// Get heat results
results, err := s.db.GetHeatResults(currentGroup.ID)
if err != nil {
s.logger.Error("Failed to get heat results", "error", err)
http.Error(w, "Failed to get heat results", http.StatusInternalServerError)
return
}
// Get on-deck heat data
onDeckHeatData, _ := s.db.GetHeatData(currentGroup.ID, currentHeatNum+2)
// Render template
component := templates.RacePublic(currentGroup, heats, racers, currentHeatNum, results)
component := templates.RacePublic(heatData, nextHeatData, onDeckHeatData)
if err := component.Render(r.Context(), w); err != nil {
s.logger.Error("Failed to render race public template", "error", err)
http.Error(w, "Failed to render page", http.StatusInternalServerError)

@ -0,0 +1 @@
(function(){var g;htmx.defineExtension("sse",{init:function(e){g=e;if(htmx.createEventSource==undefined){htmx.createEventSource=t}},getSelectors:function(){return["[sse-connect]","[data-sse-connect]","[sse-swap]","[data-sse-swap]"]},onEvent:function(e,t){var r=t.target||t.detail.elt;switch(e){case"htmx:beforeCleanupElement":var n=g.getInternalData(r);var s=n.sseEventSource;if(s){g.triggerEvent(r,"htmx:sseClose",{source:s,type:"nodeReplaced"});n.sseEventSource.close()}return;case"htmx:afterProcessNode":i(r)}}});function t(e){return new EventSource(e,{withCredentials:true})}function a(n){if(g.getAttributeValue(n,"sse-swap")){var s=g.getClosestMatch(n,v);if(s==null){return null}var e=g.getInternalData(s);var a=e.sseEventSource;var t=g.getAttributeValue(n,"sse-swap");var r=t.split(",");for(var i=0;i<r.length;i++){const u=r[i].trim();const c=function(e){if(l(s)){return}if(!g.bodyContains(n)){a.removeEventListener(u,c);return}if(!g.triggerEvent(n,"htmx:sseBeforeMessage",e)){return}f(n,e.data);g.triggerEvent(n,"htmx:sseMessage",e)};g.getInternalData(n).sseEventListener=c;a.addEventListener(u,c)}}if(g.getAttributeValue(n,"hx-trigger")){var s=g.getClosestMatch(n,v);if(s==null){return null}var e=g.getInternalData(s);var a=e.sseEventSource;var o=g.getTriggerSpecs(n);o.forEach(function(t){if(t.trigger.slice(0,4)!=="sse:"){return}var r=function(e){if(l(s)){return}if(!g.bodyContains(n)){a.removeEventListener(t.trigger.slice(4),r)}htmx.trigger(n,t.trigger,e);htmx.trigger(n,"htmx:sseMessage",e)};g.getInternalData(n).sseEventListener=r;a.addEventListener(t.trigger.slice(4),r)})}}function i(e,t){if(e==null){return null}if(g.getAttributeValue(e,"sse-connect")){var r=g.getAttributeValue(e,"sse-connect");if(r==null){return}n(e,r,t)}a(e)}function n(r,e,n){var s=htmx.createEventSource(e);s.onerror=function(e){g.triggerErrorEvent(r,"htmx:sseError",{error:e,source:s});if(l(r)){return}if(s.readyState===EventSource.CLOSED){n=n||0;n=Math.max(Math.min(n*2,128),1);var t=n*500;window.setTimeout(function(){i(r,n)},t)}};s.onopen=function(e){g.triggerEvent(r,"htmx:sseOpen",{source:s});if(n&&n>0){const t=r.querySelectorAll("[sse-swap], [data-sse-swap], [hx-trigger], [data-hx-trigger]");for(let e=0;e<t.length;e++){a(t[e])}n=0}};g.getInternalData(r).sseEventSource=s;var t=g.getAttributeValue(r,"sse-close");if(t){s.addEventListener(t,function(){g.triggerEvent(r,"htmx:sseClose",{source:s,type:"message"});s.close()})}}function l(e){if(!g.bodyContains(e)){var t=g.getInternalData(e).sseEventSource;if(t!=undefined){g.triggerEvent(e,"htmx:sseClose",{source:t,type:"nodeMissing"});t.close();return true}}return false}function f(t,r){g.withExtensions(t,function(e){r=e.transformResponse(r,null,t)});var e=g.getSwapSpecification(t);var n=g.getTarget(t);g.swap(n,r,e)}function v(e){return g.getInternalData(e).sseEventSource!=null}})();

@ -7,14 +7,14 @@ import (
)
// RacePublic renders the public race view
templ RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) {
@LayoutPublic("Race - " + currentGroup.Name) {
templ RacePublic(heatData *models.HeatData, nextHeat *models.HeatData, onDeckHeat *models.HeatData) {
@LayoutPublic("Race - " + heatData.Group.Name) {
<div class="container-fluid mt-3">
<div class="row">
<div class="col-12">
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h2 class="mb-0 text-center">{ currentGroup.Name } - Heat { strconv.Itoa(currentHeatNum) } of { strconv.Itoa(len(heats)) }</h2>
<h2 class="mb-0 text-center">{ heatData.Group.Name } - Heat { strconv.Itoa(heatData.HeatNumber) } of { strconv.Itoa(heatData.TotalHeats) }</h2>
</div>
<div class="card-body">
<div id="current-heat" class="mb-4">
@ -26,7 +26,7 @@ templ RacePublic(currentGroup models.Group, heats []models.Heat, racers []models
</div>
<div class="lanes-container">
@raceCurrentHeatLanes(heats, racers, currentHeatNum, results)
@raceCurrentHeatLanes(heatData)
</div>
</div>
</div>
@ -43,8 +43,8 @@ templ RacePublic(currentGroup models.Group, heats []models.Heat, racers []models
<h3 class="mb-0">Next Heat</h3>
</div>
<div class="card-body">
if currentHeatNum < len(heats) {
@raceNextHeatPreview(heats, racers, currentHeatNum+1)
if nextHeat != nil {
@raceNextHeatPreview(nextHeat)
} else {
<div class="alert alert-info">No more heats in this group</div>
}
@ -57,8 +57,8 @@ templ RacePublic(currentGroup models.Group, heats []models.Heat, racers []models
<h3 class="mb-0">Upcoming Heat</h3>
</div>
<div class="card-body">
if currentHeatNum+1 < len(heats) {
@raceNextHeatPreview(heats, racers, currentHeatNum+2)
if onDeckHeat != nil {
@raceNextHeatPreview(onDeckHeat)
} else {
<div class="alert alert-info">No more heats in this group</div>
}
@ -67,214 +67,56 @@ templ RacePublic(currentGroup models.Group, heats []models.Heat, racers []models
</div>
</div>
</div>
<script>
// Set up SSE connection
console.log("Setting up SSE connection...");
const eventSource = new EventSource('/api/events');
eventSource.onopen = function() {
console.log("SSE connection opened");
};
eventSource.onerror = function(error) {
console.error("SSE connection error:", error);
};
eventSource.addEventListener('debug', function(event) {
console.log("Debug event received:", event.data);
});
eventSource.addEventListener('lane-finish', function(event) {
console.log("Lane finish event received:", event.data);
try {
const laneFinishData = JSON.parse(event.data);
const laneTimeElement = document.getElementById(`lane-${laneFinishData.lane}-time`);
if (laneTimeElement) {
laneTimeElement.textContent = laneFinishData.time.toFixed(4);
}
const lanePositionElement = document.getElementById(`lane-${laneFinishData.lane}-position`);
if (lanePositionElement) {
lanePositionElement.textContent = getOrdinal(laneFinishData.place);
}
// Highlight the lane card
const laneCard = document.querySelector(`.lane-card[data-lane="${laneFinishData.lane}"]`);
if (laneCard) {
laneCard.classList.add('bg-success-subtle');
}
} catch (error) {
console.error("Error processing lane finish event:", error);
}
});
eventSource.addEventListener('status', function(event) {
console.log("Status event received:", event.data);
try {
const statusData = JSON.parse(event.data);
let statusText = 'Unknown';
let statusClass = 'bg-secondary';
if (statusData.status === 'idle') {
statusText = 'Ready';
statusClass = 'bg-primary';
// Don't reset times and positions here anymore
// Only reset visual styling
document.querySelectorAll('.lane-card').forEach(lane => {
lane.classList.remove('bg-success-subtle');
});
} else if (statusData.status === 'running') {
statusText = 'Race Running';
statusClass = 'bg-success';
} else if (statusData.status === 'finished') {
statusText = 'Race Complete';
statusClass = 'bg-info';
// Don't reload the page automatically
// Let the race manager control when to move to the next heat
}
const statusIndicator = document.getElementById('status-indicator');
if (statusIndicator) {
statusIndicator.textContent = statusText;
statusIndicator.className = `badge ${statusClass}`;
}
const timerDisplay = document.getElementById('timer');
if (timerDisplay && statusData.status === 'idle') {
timerDisplay.textContent = '0.000';
}
} catch (error) {
console.error("Error processing status event:", error);
}
});
function getOrdinal(n) {
const s = ["th", "st", "nd", "rd"];
const v = n % 100;
return n + (s[(v-20)%10] || s[v] || s[0]);
}
// Auto-refresh the page every 30 seconds to keep data current
setTimeout(() => {
window.location.reload();
}, 30000);
</script>
}
}
// Helper template for displaying current heat lanes
templ raceCurrentHeatLanes(heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) {
{{
// Find current heat
var currentHeat models.Heat
for _, heat := range heats {
if heat.HeatNum == currentHeatNum {
currentHeat = heat
break
}
}
// Find heat result if available
var currentResult *models.HeatResult
for _, result := range results {
if result.HeatNumber == currentHeatNum {
currentResult = &result
break
}
}
}}
templ raceCurrentHeatLanes(heatData *models.HeatData) {
<div class="row row-cols-1 row-cols-md-4 g-4">
if currentHeat.Lane1ID != nil {
@raceLaneCard(1, *currentHeat.Lane1ID, racers, currentResult)
if heatData.Lane1 != nil {
@raceLaneCard(*heatData.Lane1)
}
if currentHeat.Lane2ID != nil {
@raceLaneCard(2, *currentHeat.Lane2ID, racers, currentResult)
if heatData.Lane2 != nil {
@raceLaneCard(*heatData.Lane2)
}
if currentHeat.Lane3ID != nil {
@raceLaneCard(3, *currentHeat.Lane3ID, racers, currentResult)
if heatData.Lane3 != nil {
@raceLaneCard(*heatData.Lane3)
}
if currentHeat.Lane4ID != nil {
@raceLaneCard(4, *currentHeat.Lane4ID, racers, currentResult)
if heatData.Lane4 != nil {
@raceLaneCard(*heatData.Lane4)
}
</div>
}
// Helper template for displaying a lane card
templ raceLaneCard(lane int, racerID int64, racers []models.Racer, result *models.HeatResult) {
{{
// Find racer
var racer models.Racer
for _, r := range racers {
if r.ID == racerID {
racer = r
break
}
}
// Get time and position from result if available
var time float64
var position int
var hasResult bool
if result != nil {
hasResult = true
switch lane {
case 1:
time = result.Lane1Time
position = result.Lane1Position
case 2:
time = result.Lane2Time
position = result.Lane2Position
case 3:
time = result.Lane3Time
position = result.Lane3Position
case 4:
time = result.Lane4Time
position = result.Lane4Position
}
}
}}
templ raceLaneCard(laneData models.LaneData) {
<div class="col">
<div class="card h-100 lane-card" data-lane="{ strconv.Itoa(lane) }">
<div class="card h-100 lane-card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Lane { strconv.Itoa(lane) }</h4>
<h4 class="mb-0">Lane { strconv.Itoa(laneData.Lane) }</h4>
</div>
<div class="card-body">
<h5 class="card-title">{ racer.FirstName } { racer.LastName }</h5>
<h5 class="card-title">{ laneData.Name }</h5>
<p class="card-text">
<strong>Car #:</strong> { racer.CarNumber }<br/>
<strong>Weight:</strong> { fmt.Sprintf("%.1f oz", racer.CarWeight) }
<strong>Car #:</strong> { laneData.CarNum }<br/>
<strong>Weight:</strong> { fmt.Sprintf("%.1f oz", laneData.CarWeight) }
</p>
<div class="result-area">
<div class="row">
<div class="col-6">
<div class="text-center">
<h6>Time</h6>
<div id={ fmt.Sprintf("lane-%d-time", lane) } class="display-6">
if hasResult {
{ fmt.Sprintf("%.3f", time) }
} else {
--.-
}
<div class="display-6">
{ fmt.Sprintf("%.3f", laneData.Time) }
</div>
</div>
</div>
<div class="col-6">
<div class="text-center">
<h6>Position</h6>
<div id={ fmt.Sprintf("lane-%d-position", lane) } class="display-6">
if hasResult {
{ strconv.Itoa(position) }
} else {
-
}
<div class="display-6">
{ strconv.Itoa(laneData.Place) }
</div>
</div>
</div>
@ -286,19 +128,8 @@ templ raceLaneCard(lane int, racerID int64, racers []models.Racer, result *model
}
// Helper template for displaying next heat preview
templ raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum int) {
{{
// Find the heat
var nextHeat models.Heat
for _, heat := range heats {
if heat.HeatNum == heatNum {
nextHeat = heat
break
}
}
}}
<h4 class="mb-3">Heat { strconv.Itoa(heatNum) }</h4>
templ raceNextHeatPreview(heatData *models.HeatData) {
<h4 class="mb-3">Heat { strconv.Itoa(heatData.HeatNumber) }</h4>
<div class="table-responsive">
<table class="table table-striped">
<thead>
@ -309,17 +140,17 @@ templ raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum in
</tr>
</thead>
<tbody>
if nextHeat.Lane1ID != nil {
@raceNextHeatRow(1, *nextHeat.Lane1ID, racers)
if heatData.Lane1 != nil {
@raceNextHeatRow(*heatData.Lane1)
}
if nextHeat.Lane2ID != nil {
@raceNextHeatRow(2, *nextHeat.Lane2ID, racers)
if heatData.Lane2 != nil {
@raceNextHeatRow(*heatData.Lane2)
}
if nextHeat.Lane3ID != nil {
@raceNextHeatRow(3, *nextHeat.Lane3ID, racers)
if heatData.Lane3 != nil {
@raceNextHeatRow(*heatData.Lane3)
}
if nextHeat.Lane4ID != nil {
@raceNextHeatRow(4, *nextHeat.Lane4ID, racers)
if heatData.Lane4 != nil {
@raceNextHeatRow(*heatData.Lane4)
}
</tbody>
</table>
@ -327,21 +158,10 @@ templ raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum in
}
// Helper template for displaying a row in the next heat preview
templ raceNextHeatRow(lane int, racerID int64, racers []models.Racer) {
{{
// Find racer
var racer models.Racer
for _, r := range racers {
if r.ID == racerID {
racer = r
break
}
}
}}
templ raceNextHeatRow(laneData models.LaneData) {
<tr>
<td>{ strconv.Itoa(lane) }</td>
<td>{ racer.FirstName } { racer.LastName }</td>
<td>{ racer.CarNumber }</td>
<td>{ strconv.Itoa(laneData.Lane) }</td>
<td>{ laneData.Name }</td>
<td>{ laneData.CarNum }</td>
</tr>
}

@ -15,7 +15,7 @@ import (
)
// RacePublic renders the public race view
func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) templ.Component {
func RacePublic(heatData *models.HeatData, nextHeat *models.HeatData, onDeckHeat *models.HeatData) 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 {
@ -53,9 +53,9 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(currentGroup.Name)
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(heatData.Group.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 76}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 78}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@ -66,9 +66,9 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(currentHeatNum))
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(heatData.HeatNumber))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 116}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 123}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@ -79,9 +79,9 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(heats)))
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(heatData.TotalHeats))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 148}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 17, Col: 164}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@ -91,7 +91,7 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = raceCurrentHeatLanes(heats, racers, currentHeatNum, results).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = raceCurrentHeatLanes(heatData).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -99,8 +99,8 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if currentHeatNum < len(heats) {
templ_7745c5c3_Err = raceNextHeatPreview(heats, racers, currentHeatNum+1).Render(ctx, templ_7745c5c3_Buffer)
if nextHeat != nil {
templ_7745c5c3_Err = raceNextHeatPreview(nextHeat).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -114,8 +114,8 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if currentHeatNum+1 < len(heats) {
templ_7745c5c3_Err = raceNextHeatPreview(heats, racers, currentHeatNum+2).Render(ctx, templ_7745c5c3_Buffer)
if onDeckHeat != nil {
templ_7745c5c3_Err = raceNextHeatPreview(onDeckHeat).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -125,13 +125,13 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div></div></div></div><script>\r\n // Set up SSE connection\r\n console.log(\"Setting up SSE connection...\");\r\n \r\n const eventSource = new EventSource('/api/events');\r\n \r\n eventSource.onopen = function() {\r\n console.log(\"SSE connection opened\");\r\n };\r\n \r\n eventSource.onerror = function(error) {\r\n console.error(\"SSE connection error:\", error);\r\n };\r\n \r\n eventSource.addEventListener('debug', function(event) {\r\n console.log(\"Debug event received:\", event.data);\r\n });\r\n \r\n eventSource.addEventListener('lane-finish', function(event) {\r\n console.log(\"Lane finish event received:\", event.data);\r\n try {\r\n const laneFinishData = JSON.parse(event.data);\r\n const laneTimeElement = document.getElementById(`lane-${laneFinishData.lane}-time`);\r\n if (laneTimeElement) {\r\n laneTimeElement.textContent = laneFinishData.time.toFixed(4);\r\n }\r\n \r\n const lanePositionElement = document.getElementById(`lane-${laneFinishData.lane}-position`);\r\n if (lanePositionElement) {\r\n lanePositionElement.textContent = getOrdinal(laneFinishData.place);\r\n }\r\n \r\n // Highlight the lane card\r\n const laneCard = document.querySelector(`.lane-card[data-lane=\"${laneFinishData.lane}\"]`);\r\n if (laneCard) {\r\n laneCard.classList.add('bg-success-subtle');\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing lane finish event:\", error);\r\n }\r\n });\r\n \r\n eventSource.addEventListener('status', function(event) {\r\n console.log(\"Status event received:\", event.data);\r\n try {\r\n const statusData = JSON.parse(event.data);\r\n let statusText = 'Unknown';\r\n let statusClass = 'bg-secondary';\r\n \r\n if (statusData.status === 'idle') {\r\n statusText = 'Ready';\r\n statusClass = 'bg-primary';\r\n \r\n // Don't reset times and positions here anymore\r\n // Only reset visual styling\r\n document.querySelectorAll('.lane-card').forEach(lane => {\r\n lane.classList.remove('bg-success-subtle');\r\n });\r\n \r\n } else if (statusData.status === 'running') {\r\n statusText = 'Race Running';\r\n statusClass = 'bg-success';\r\n } else if (statusData.status === 'finished') {\r\n statusText = 'Race Complete';\r\n statusClass = 'bg-info';\r\n \r\n // Don't reload the page automatically\r\n // Let the race manager control when to move to the next heat\r\n }\r\n \r\n const statusIndicator = document.getElementById('status-indicator');\r\n if (statusIndicator) {\r\n statusIndicator.textContent = statusText;\r\n statusIndicator.className = `badge ${statusClass}`;\r\n }\r\n \r\n const timerDisplay = document.getElementById('timer');\r\n if (timerDisplay && statusData.status === 'idle') {\r\n timerDisplay.textContent = '0.000';\r\n }\r\n } catch (error) {\r\n console.error(\"Error processing status event:\", error);\r\n }\r\n });\r\n \r\n function getOrdinal(n) {\r\n const s = [\"th\", \"st\", \"nd\", \"rd\"];\r\n const v = n % 100;\r\n return n + (s[(v-20)%10] || s[v] || s[0]);\r\n }\r\n \r\n // Auto-refresh the page every 30 seconds to keep data current\r\n setTimeout(() => {\r\n window.location.reload();\r\n }, 30000);\r\n </script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = LayoutPublic("Race - "+currentGroup.Name).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = LayoutPublic("Race - "+heatData.Group.Name).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -140,7 +140,7 @@ func RacePublic(currentGroup models.Group, heats []models.Heat, racers []models.
}
// Helper template for displaying current heat lanes
func raceCurrentHeatLanes(heats []models.Heat, racers []models.Racer, currentHeatNum int, results []models.HeatResult) templ.Component {
func raceCurrentHeatLanes(heatData *models.HeatData) 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 {
@ -161,48 +161,30 @@ func raceCurrentHeatLanes(heats []models.Heat, racers []models.Racer, currentHea
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
// Find current heat
var currentHeat models.Heat
for _, heat := range heats {
if heat.HeatNum == currentHeatNum {
currentHeat = heat
break
}
}
// Find heat result if available
var currentResult *models.HeatResult
for _, result := range results {
if result.HeatNumber == currentHeatNum {
currentResult = &result
break
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"row row-cols-1 row-cols-md-4 g-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if currentHeat.Lane1ID != nil {
templ_7745c5c3_Err = raceLaneCard(1, *currentHeat.Lane1ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane1 != nil {
templ_7745c5c3_Err = raceLaneCard(*heatData.Lane1).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if currentHeat.Lane2ID != nil {
templ_7745c5c3_Err = raceLaneCard(2, *currentHeat.Lane2ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane2 != nil {
templ_7745c5c3_Err = raceLaneCard(*heatData.Lane2).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if currentHeat.Lane3ID != nil {
templ_7745c5c3_Err = raceLaneCard(3, *currentHeat.Lane3ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane3 != nil {
templ_7745c5c3_Err = raceLaneCard(*heatData.Lane3).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if currentHeat.Lane4ID != nil {
templ_7745c5c3_Err = raceLaneCard(4, *currentHeat.Lane4ID, racers, currentResult).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane4 != nil {
templ_7745c5c3_Err = raceLaneCard(*heatData.Lane4).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -216,7 +198,7 @@ func raceCurrentHeatLanes(heats []models.Heat, racers []models.Racer, currentHea
}
// Helper template for displaying a lane card
func raceLaneCard(lane int, racerID int64, racers []models.Racer, result *models.HeatResult) templ.Component {
func raceLaneCard(laneData models.LaneData) 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 {
@ -237,46 +219,14 @@ func raceLaneCard(lane int, racerID int64, racers []models.Racer, result *models
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
// Find racer
var racer models.Racer
for _, r := range racers {
if r.ID == racerID {
racer = r
break
}
}
// Get time and position from result if available
var time float64
var position int
var hasResult bool
if result != nil {
hasResult = true
switch lane {
case 1:
time = result.Lane1Time
position = result.Lane1Position
case 2:
time = result.Lane2Time
position = result.Lane2Position
case 3:
time = result.Lane3Time
position = result.Lane3Position
case 4:
time = result.Lane4Time
position = result.Lane4Position
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"col\"><div class=\"card h-100 lane-card\" data-lane=\"{ strconv.Itoa(lane) }\"><div class=\"card-header bg-primary text-white\"><h4 class=\"mb-0\">Lane ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"col\"><div class=\"card h-100 lane-card\"><div class=\"card-header bg-primary text-white\"><h4 class=\"mb-0\">Lane ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(lane))
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(laneData.Lane))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 247, Col: 58}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 97, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@ -287,120 +237,67 @@ func raceLaneCard(lane int, racerID int64, racers []models.Racer, result *models
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName)
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(laneData.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 250, Col: 56}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 100, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</h5><p class=\"card-text\"><strong>Car #:</strong> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(racer.LastName)
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(laneData.CarNum)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 250, Col: 75}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 102, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h5><p class=\"card-text\"><strong>Car #:</strong> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<br><strong>Weight:</strong> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber)
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f oz", laneData.CarWeight))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 252, Col: 61}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 103, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<br><strong>Weight:</strong> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</p><div class=\"result-area\"><div class=\"row\"><div class=\"col-6\"><div class=\"text-center\"><h6>Time</h6><div class=\"display-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f oz", racer.CarWeight))
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f", laneData.Time))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 253, Col: 86}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 111, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</p><div class=\"result-area\"><div class=\"row\"><div class=\"col-6\"><div class=\"text-center\"><h6>Time</h6><div id=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div></div></div><div class=\"col-6\"><div class=\"text-center\"><h6>Position</h6><div class=\"display-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("lane-%d-time", lane))
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(laneData.Place))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 260, Col: 75}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 119, Col: 66}
}
_, 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, 18, "\" class=\"display-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if hasResult {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.3f", time))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 262, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "--.-")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div></div></div><div class=\"col-6\"><div class=\"text-center\"><h6>Position</h6><div id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("lane-%d-position", lane))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 272, Col: 79}
}
_, 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, 21, "\" class=\"display-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if hasResult {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(position))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 274, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "-")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</div></div></div></div></div></div></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div></div></div></div></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -409,7 +306,7 @@ func raceLaneCard(lane int, racerID int64, racers []models.Racer, result *models
}
// Helper template for displaying next heat preview
func raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum int) templ.Component {
func raceNextHeatPreview(heatData *models.HeatData) 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 {
@ -425,62 +322,53 @@ func raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum int
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var17 := templ.GetChildren(ctx)
if templ_7745c5c3_Var17 == nil {
templ_7745c5c3_Var17 = templ.NopComponent
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
if templ_7745c5c3_Var14 == nil {
templ_7745c5c3_Var14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
// Find the heat
var nextHeat models.Heat
for _, heat := range heats {
if heat.HeatNum == heatNum {
nextHeat = heat
break
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<h4 class=\"mb-3\">Heat ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h4 class=\"mb-3\">Heat ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(heatNum))
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(heatData.HeatNumber))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 301, Col: 49}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 132, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
_, 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, 25, "</h4><div class=\"table-responsive\"><table class=\"table table-striped\"><thead><tr><th>Lane</th><th>Racer</th><th>Car #</th></tr></thead> <tbody>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</h4><div class=\"table-responsive\"><table class=\"table table-striped\"><thead><tr><th>Lane</th><th>Racer</th><th>Car #</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if nextHeat.Lane1ID != nil {
templ_7745c5c3_Err = raceNextHeatRow(1, *nextHeat.Lane1ID, racers).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane1 != nil {
templ_7745c5c3_Err = raceNextHeatRow(*heatData.Lane1).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if nextHeat.Lane2ID != nil {
templ_7745c5c3_Err = raceNextHeatRow(2, *nextHeat.Lane2ID, racers).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane2 != nil {
templ_7745c5c3_Err = raceNextHeatRow(*heatData.Lane2).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if nextHeat.Lane3ID != nil {
templ_7745c5c3_Err = raceNextHeatRow(3, *nextHeat.Lane3ID, racers).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane3 != nil {
templ_7745c5c3_Err = raceNextHeatRow(*heatData.Lane3).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if nextHeat.Lane4ID != nil {
templ_7745c5c3_Err = raceNextHeatRow(4, *nextHeat.Lane4ID, racers).Render(ctx, templ_7745c5c3_Buffer)
if heatData.Lane4 != nil {
templ_7745c5c3_Err = raceNextHeatRow(*heatData.Lane4).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</tbody></table></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</tbody></table></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -489,7 +377,7 @@ func raceNextHeatPreview(heats []models.Heat, racers []models.Racer, heatNum int
}
// Helper template for displaying a row in the next heat preview
func raceNextHeatRow(lane int, racerID int64, racers []models.Racer) templ.Component {
func raceNextHeatRow(laneData models.LaneData) 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 {
@ -505,73 +393,51 @@ func raceNextHeatRow(lane int, racerID int64, racers []models.Racer) templ.Compo
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
if templ_7745c5c3_Var19 == nil {
templ_7745c5c3_Var19 = templ.NopComponent
templ_7745c5c3_Var16 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil {
templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
// Find racer
var racer models.Racer
for _, r := range racers {
if r.ID == racerID {
racer = r
break
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<tr><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(lane))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 343, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</td><td>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<tr><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName)
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(laneData.Lane))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 344, Col: 29}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 163, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
_, 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, 29, " ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(racer.LastName)
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(laneData.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 344, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 164, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
_, 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, 30, "</td><td>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber)
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(laneData.CarNum)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 345, Col: 29}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/race_public.templ`, Line: 165, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
_, 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, 31, "</td></tr>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

Loading…
Cancel
Save