diff --git a/web/server.go b/web/server.go index c15c326..a10bc50 100644 --- a/web/server.go +++ b/web/server.go @@ -29,16 +29,17 @@ var content embed.FS // Server represents the web server for the derby clock type Server struct { - router *chi.Mux - clock *derby.DerbyClock - events <-chan derby.Event - clients map[chan string]bool - clientsMux sync.Mutex - port int - server *http.Server - shutdown chan struct{} - logger *slog.Logger - db *db.DB + router *chi.Mux + clock *derby.DerbyClock + events <-chan derby.Event + clients map[chan string]bool + clientsMux sync.Mutex + port int + server *http.Server + shutdown chan struct{} + logger *slog.Logger + db *db.DB + adminEvents chan string } // NewServer creates a new web server @@ -51,15 +52,16 @@ func NewServer(clock *derby.DerbyClock, events <-chan derby.Event, dbPath string // Create server s := &Server{ - router: chi.NewRouter(), - clock: clock, - events: events, - clients: make(map[chan string]bool), - clientsMux: sync.Mutex{}, - port: port, - shutdown: make(chan struct{}), - logger: logger, - db: database, + router: chi.NewRouter(), + clock: clock, + events: events, + clients: make(map[chan string]bool), + clientsMux: sync.Mutex{}, + port: port, + shutdown: make(chan struct{}), + logger: logger, + db: database, + adminEvents: make(chan string, 10), } // Set up routes @@ -145,6 +147,9 @@ 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()) } // Start starts the web server @@ -440,7 +445,7 @@ func (s *Server) handleRegister() http.HandlerFunc { // handleRegisterForm returns just the registration form component func (s *Server) handleRegisterForm() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // Get groups for the form + // Get groups groups, err := s.db.GetGroups() if err != nil { s.logger.Error("Failed to get groups", "error", err) @@ -448,12 +453,11 @@ func (s *Server) handleRegisterForm() http.HandlerFunc { return } - // Render just the registration form component - component := templates.RegisterForm(groups) + // Render form with isAdmin=true + component := templates.RegisterForm(groups, true) if err := component.Render(r.Context(), w); err != nil { - s.logger.Error("Failed to render registration form", "error", err) - http.Error(w, "Failed to render registration form", http.StatusInternalServerError) - return + s.logger.Error("Failed to render form", "error", err) + http.Error(w, "Failed to render form", http.StatusInternalServerError) } } } @@ -607,6 +611,14 @@ func (s *Server) handleCreateRacer() http.HandlerFunc { s.logger.Info("Racer created", "id", id) + // Broadcast event to admin page + select { + case s.adminEvents <- "racer-added": + // Event sent + default: + // Channel full, non-blocking + } + // Return success message w.Header().Set("Content-Type", "text/html") w.Write([]byte(`
Racer added successfully!
`)) @@ -873,3 +885,26 @@ func (s *Server) handleRacersList() http.HandlerFunc { } } } + +// handleAdminEvents handles admin events with SSE +func (s *Server) handleAdminEvents() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Set SSE headers + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + // Send events to client + for { + select { + case event := <-s.adminEvents: + fmt.Fprintf(w, "data: %s\n\n", event) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + case <-r.Context().Done(): + return + } + } + } +} diff --git a/web/templates/admin.templ b/web/templates/admin.templ index e189003..d81ce63 100644 --- a/web/templates/admin.templ +++ b/web/templates/admin.templ @@ -188,6 +188,15 @@ templ Admin(groups []models.Group, racers []models.Racer) { // Update submit button form.querySelector('button[type="submit"]').textContent = 'Update Group'; } + + document.addEventListener('DOMContentLoaded', function() { + const evtSource = new EventSource("/api/admin-events"); + evtSource.onmessage = function(event) { + if (event.data === "racer-added") { + htmx.ajax('GET', '/admin/racers/list', {target: '#racers-list'}); + } + }; + }); } } diff --git a/web/templates/admin_templ.go b/web/templates/admin_templ.go index b967459..3defce7 100644 --- a/web/templates/admin_templ.go +++ b/web/templates/admin_templ.go @@ -236,7 +236,7 @@ func Admin(groups []models.Group, racers []models.Racer) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -298,7 +298,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, 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: 223, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 232, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -311,7 +311,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, 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: 224, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 233, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -324,7 +324,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(getGroupNameForRacer(groups, racer.GroupID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 225, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 234, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -337,7 +337,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs("/admin/racers/edit/" + strconv.FormatInt(racer.ID, 10)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 230, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 239, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -350,7 +350,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/api/racers/" + strconv.FormatInt(racer.ID, 10)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 237, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 246, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -363,7 +363,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs("Are you sure you want to delete " + racer.FirstName + " " + racer.LastName + "?") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 239, Col: 104} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 248, Col: 104} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { diff --git a/web/templates/register.templ b/web/templates/register.templ index fe99ea9..c3f6194 100644 --- a/web/templates/register.templ +++ b/web/templates/register.templ @@ -1,137 +1,30 @@ package templates import ( - "fmt" "strconv" "track-gopher/models" ) templ Register(groups []models.Group) { - - - - - - Racer Registration - - - - - - - + @Layout("Register Racer") {
-
-

Racer Registration

- -
+

Register for Derby Race

-
-
-
-
-
Register New Racer
-
-
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
- -
-
-
-
- -
-

Registration Successful!

-

The racer has been registered successfully.

-
-
- - View All Racers -
-
- -
-

Registration Failed

-

There was an error registering the racer.

-
- +
+
+
+ @RegisterForm(groups, false)
- - - - + } } -templ RegisterForm(groups []models.Group) { +templ RegisterForm(groups []models.Group, isAdmin bool) {
-

Add New Racer

+

Racer Registration

@@ -144,7 +37,10 @@ templ RegisterForm(groups []models.Group) { document.getElementById('racer-form').reset(); setTimeout(function() { document.getElementById('racer-form-message').innerHTML = ''; - loadRacersList(); + // Check if we're on the admin page by looking for the racers-list element + if (document.getElementById('racers-list')) { + htmx.ajax('GET', '/admin/racers/list', {target: '#racers-list'}); + } }, 3000); }" > @@ -175,16 +71,14 @@ templ RegisterForm(groups []models.Group) {
- - + if isAdmin { + + + } else { + + }
- - } \ No newline at end of file diff --git a/web/templates/register_templ.go b/web/templates/register_templ.go index c4d7a55..bbd5f14 100644 --- a/web/templates/register_templ.go +++ b/web/templates/register_templ.go @@ -9,7 +9,6 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( - "fmt" "strconv" "track-gopher/models" ) @@ -35,43 +34,33 @@ func Register(groups []models.Group) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Racer Registration

Racer Registration

Register New Racer

Registration Successful!

The racer has been registered successfully.


View All Racers

Registration Failed

There was an error registering the racer.


") + return nil + }) + templ_7745c5c3_Err = Layout("Register Racer").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -79,7 +68,7 @@ func Register(groups []models.Group) templ.Component { }) } -func RegisterForm(groups []models.Group) templ.Component { +func RegisterForm(groups []models.Group, isAdmin bool) 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 { @@ -95,48 +84,63 @@ func RegisterForm(groups []models.Group) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var4 := templ.GetChildren(ctx) - if templ_7745c5c3_Var4 == nil { - templ_7745c5c3_Var4 = templ.NopComponent + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Add New Racer

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if isAdmin { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }