feat: add async tour system

Players can create tours with selected courses/layouts and a date range.
Others join via a 6-character tour code, play the courses, and report
their total strokes. Live leaderboard with points and +/- par display.

Includes: database schema, model, service, routes, views, and styling.
This commit is contained in:
Samuel Enocsson
2026-03-19 22:47:54 +01:00
parent d567c4bca9
commit 2ccb018bdf
14 changed files with 2489 additions and 0 deletions
+1
View File
@@ -1,4 +1,5 @@
<nav class="app-nav">
<a href="/" class="<%= activePage === 'players' ? 'active' : '' %>">Players</a>
<a href="/courses" class="<%= activePage === 'courses' ? 'active' : '' %>">Courses</a>
<a href="/tours" class="<%= activePage === 'tours' ? 'active' : '' %>">Tours</a>
</nav>
+52
View File
@@ -0,0 +1,52 @@
<% if (leaderboard.length === 0) { %>
<div class="card-section">
<p style="text-align: center; color: var(--text-secondary);">No players have joined yet. Share the tour link to get started!</p>
</div>
<% } else { %>
<div class="card-section">
<h3>Leaderboard</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Player</th>
<% courses.forEach(function(c) { %>
<th class="mobile-hide"><%= c.course_name %><br><small><%= c.layout_name %></small></th>
<% }); %>
<th>Points</th>
</tr>
</thead>
<tbody>
<% var rank = 1; %>
<% leaderboard.forEach(function(player, i) { %>
<% if (i > 0 && player.total_points < leaderboard[i-1].total_points) rank = i + 1; %>
<tr>
<td><strong><%= rank %></strong></td>
<td>
<%= player.player_name %>
<span style="color: var(--text-muted); font-size: 12px;"><%= player.pdga_number %></span>
</td>
<% courses.forEach(function(c) { %>
<td class="mobile-hide">
<% var result = player.courses[c.tour_course_id]; %>
<% if (result) { %>
<span class="strokes"><%= result.total_strokes %></span>
<% if (result.relative_par > 0) { %>
<span class="over-par">(+<%= result.relative_par %>)</span>
<% } else if (result.relative_par < 0) { %>
<span class="under-par">(<%= result.relative_par %>)</span>
<% } else { %>
<span class="even-par">(E)</span>
<% } %>
<% } else { %>
<span style="color: var(--text-muted);">-</span>
<% } %>
</td>
<% }); %>
<td><strong><%= player.total_points %></strong></td>
</tr>
<% }); %>
</tbody>
</table>
</div>
<% } %>