Merge remote-tracking branch 'origin/main' into feat/show-excluded-rounds-count-21
This commit is contained in:
+26
-37
@@ -1,43 +1,32 @@
|
||||
<% var body = `
|
||||
<div class="card-section">
|
||||
<h3>Find Courses</h3>
|
||||
<div class="card-section-form">
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
id="course-search"
|
||||
name="q"
|
||||
placeholder="Search courses by name or city..."
|
||||
hx-get="/partials/course-table"
|
||||
hx-trigger="input changed delay:300ms, search"
|
||||
hx-target="#courses-table"
|
||||
style="width: 340px;"
|
||||
/>
|
||||
<button class="btn" onclick="scrapeCourses()" id="scrape-courses-btn">
|
||||
<i class="fas fa-sync-alt"></i> Scrape Courses
|
||||
</button>
|
||||
</div>
|
||||
<main class="page-courses">
|
||||
<section class="action-card">
|
||||
<div class="action-card-tabs" role="tablist">
|
||||
<button class="action-tab is-active" role="tab" aria-selected="true" data-tab="find" id="tab-find">Find courses</button>
|
||||
<button class="action-tab" role="tab" aria-selected="false" data-tab="tjing" id="tab-tjing">Import from Tjing</button>
|
||||
</div>
|
||||
<div class="action-card-body">
|
||||
<div class="action-pane is-active" id="tab-pane-find" role="tabpanel" aria-labelledby="tab-find">
|
||||
<input type="text" id="course-filter-input" placeholder="Find a course…" autocomplete="off">
|
||||
<p class="action-hint">Filters the list below as you type.</p>
|
||||
</div>
|
||||
<div class="action-pane" id="tab-pane-tjing" role="tabpanel" aria-labelledby="tab-tjing" hidden>
|
||||
<div class="tjing-search-row">
|
||||
<input type="text" id="tjing-search-input" placeholder="Search Tjing courses…" autocomplete="off">
|
||||
<button id="tjing-search-btn" class="btn-primary" onclick="searchTjing()">Search Tjing</button>
|
||||
</div>
|
||||
<p class="action-hint">Find and import Swedish courses from tjing.se.</p>
|
||||
<div id="tjing-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Mobile tab pill (visible only on mobile via CSS) -->
|
||||
<div class="m-tab-pill">
|
||||
<button class="m-tab-pill__btn m-tab-pill__btn--active" type="button">Find courses</button>
|
||||
<button class="m-tab-pill__btn m-tab-pill__btn--disabled" type="button" disabled>Import from Tjing</button>
|
||||
</div>
|
||||
<div class="results-bar">
|
||||
<span class="results-count">Showing <strong id="visible-count">0</strong> of <strong id="total-count">0</strong> courses</span>
|
||||
</div>
|
||||
|
||||
<!-- Mobile search input (hidden on desktop, shown on mobile via CSS) -->
|
||||
<input
|
||||
type="text"
|
||||
class="m-search-input"
|
||||
id="course-search-mobile"
|
||||
name="q"
|
||||
placeholder="Search courses by name or city..."
|
||||
hx-get="/partials/course-table"
|
||||
hx-trigger="input changed delay:300ms, search"
|
||||
hx-target="#courses-table"
|
||||
/>
|
||||
|
||||
<div id="courses-table" hx-get="/partials/course-table" hx-trigger="load"></div>
|
||||
<div id="course-table-region" hx-get="/partials/course-table" hx-trigger="load, refresh from:body" hx-swap="innerHTML"></div>
|
||||
</main>
|
||||
`; %>
|
||||
|
||||
<%- include('../partials/layout', {
|
||||
@@ -46,4 +35,4 @@
|
||||
cssFiles: ['courses.css'],
|
||||
jsFiles: ['courses.js'],
|
||||
body: body
|
||||
}) %>
|
||||
}) %>
|
||||
|
||||
@@ -1,71 +1,67 @@
|
||||
<% if (layouts.length === 0) { %>
|
||||
<div class="no-layouts">No layouts found. Click the refresh icon to scrape layouts.</div>
|
||||
<% if (!layouts || layouts.length === 0) { %>
|
||||
<div class="no-layouts">No layouts found. Click the refresh button to scrape layouts.</div>
|
||||
<% } else {
|
||||
var oneYearAgo = new Date();
|
||||
oneYearAgo.setDate(oneYearAgo.getDate() - 365);
|
||||
|
||||
var activeLayouts = [];
|
||||
var inactiveLayouts = [];
|
||||
|
||||
layouts.forEach(function(layout) {
|
||||
if (layout.last_played) {
|
||||
var lastPlayedDate = new Date(layout.last_played);
|
||||
if (lastPlayedDate >= oneYearAgo) {
|
||||
activeLayouts.push(layout);
|
||||
} else {
|
||||
inactiveLayouts.push(layout);
|
||||
}
|
||||
} else {
|
||||
inactiveLayouts.push(layout);
|
||||
}
|
||||
});
|
||||
var oneYearAgo = new Date();
|
||||
oneYearAgo.setDate(oneYearAgo.getDate() - 365);
|
||||
var activeLayouts = [];
|
||||
var inactiveLayouts = [];
|
||||
layouts.forEach(function(l) {
|
||||
if (l.last_played && new Date(l.last_played) >= oneYearAgo) {
|
||||
activeLayouts.push(l);
|
||||
} else {
|
||||
inactiveLayouts.push(l);
|
||||
}
|
||||
});
|
||||
var RATING_TIER_HIGH = 970;
|
||||
var RATING_TIER_MID = 940;
|
||||
function ratingTier(r) {
|
||||
if (r == null) return null;
|
||||
if (r >= RATING_TIER_HIGH) return 'green';
|
||||
if (r >= RATING_TIER_MID) return 'amber';
|
||||
return 'orange';
|
||||
}
|
||||
%>
|
||||
<h4>Layouts:</h4>
|
||||
|
||||
<% if (activeLayouts.length > 0) { %>
|
||||
<% activeLayouts.forEach(function(layout) {
|
||||
var ratingDisplay = layout.mean_rating ?
|
||||
'<span style="color: var(--green); font-weight: 700; margin-left: 10px;">Rating: ' + layout.mean_rating + '</span>' : '';
|
||||
var dateDisplay = layout.last_played ?
|
||||
'<span style="color: var(--text-muted); font-size: 12px; margin-left: 10px;">Last played: ' + new Date(layout.last_played).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) + '</span>' : '';
|
||||
%>
|
||||
<div class="layout-item">
|
||||
<div>
|
||||
<span class="layout-name"><%= layout.name %></span>
|
||||
<%- dateDisplay %>
|
||||
</div>
|
||||
<span class="layout-par">Par <%= layout.par %><%- ratingDisplay %></span>
|
||||
</div>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
|
||||
<% if (inactiveLayouts.length > 0) { %>
|
||||
<div class="inactive-layouts-accordion">
|
||||
<div class="accordion-header" onclick="toggleAccordion('accordion-<%= courseId %>')">
|
||||
<span class="accordion-header-text">Inactive Layouts (<%= inactiveLayouts.length %>) - Not played in last year</span>
|
||||
<span class="accordion-icon" id="accordion-<%= courseId %>-icon">▼</span>
|
||||
</div>
|
||||
<div class="accordion-content" id="accordion-<%= courseId %>">
|
||||
<% inactiveLayouts.forEach(function(layout) {
|
||||
var ratingDisplay = layout.mean_rating ?
|
||||
'<span style="color: var(--green); font-weight: 700; margin-left: 10px;">Rating: ' + layout.mean_rating + '</span>' : '';
|
||||
var dateDisplay = layout.last_played ?
|
||||
'<span style="color: var(--text-muted); font-size: 12px; margin-left: 10px;">Last played: ' + new Date(layout.last_played).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) + '</span>' :
|
||||
'<span style="color: var(--red); font-size: 12px; margin-left: 10px;">Never played</span>';
|
||||
%>
|
||||
<div class="layout-item inactive">
|
||||
<div>
|
||||
<span class="layout-name"><%= layout.name %></span>
|
||||
<%- dateDisplay %>
|
||||
</div>
|
||||
<span class="layout-par">Par <%= layout.par %><%- ratingDisplay %></span>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (activeLayouts.length === 0 && inactiveLayouts.length === 0) { %>
|
||||
<div class="no-layouts">No layouts found. Click the refresh icon to scrape layouts.</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<div class="layouts-header">
|
||||
<span class="layouts-kicker">LAYOUTS</span>
|
||||
<span class="layouts-count"><%= activeLayouts.length %> active · <%= inactiveLayouts.length %> inactive</span>
|
||||
</div>
|
||||
<ul class="layout-list">
|
||||
<% activeLayouts.forEach(function(l) { var tier = ratingTier(l.mean_rating); %>
|
||||
<li class="layout-card layout-card--active">
|
||||
<div class="layout-info">
|
||||
<span class="layout-name"><%= l.name %></span>
|
||||
<span class="layout-last-played">Last played: <%= l.last_played %></span>
|
||||
</div>
|
||||
<div class="layout-chips">
|
||||
<span class="chip chip-par">Par <%= l.par %></span>
|
||||
<% if (tier) { %><span class="chip chip-rating chip-rating--<%= tier %>">Rating: <%= Math.round(l.mean_rating) %></span><% } %>
|
||||
</div>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
<% if (inactiveLayouts.length > 0) { %>
|
||||
<div class="inactive-layouts">
|
||||
<button class="inactive-toggle" type="button" onclick="toggleInactiveLayouts(this)" aria-expanded="false">
|
||||
<span>Inactive layouts (<%= inactiveLayouts.length %>) — Not played in last year</span>
|
||||
<i class="icon-chev fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<ul class="layout-list inactive-layouts-body" hidden>
|
||||
<% inactiveLayouts.forEach(function(l) { %>
|
||||
<li class="layout-card layout-card--inactive">
|
||||
<div class="layout-info">
|
||||
<span class="layout-name"><%= l.name %></span>
|
||||
<% if (l.last_played) { %>
|
||||
<span class="layout-last-played">Last played: <%= l.last_played %></span>
|
||||
<% } else { %>
|
||||
<span class="layout-never-played">Never played</span>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="layout-chips">
|
||||
<span class="chip chip-par">Par <%= l.par %></span>
|
||||
</div>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
<div id="search-results-info" class="search-results-info">
|
||||
<% if (typeof query !== 'undefined' && query) { %>
|
||||
Showing <%= courses.length %> of <%= total %> courses
|
||||
<% } else { %>
|
||||
Showing all <%= courses.length %> courses
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<% if (courses.length === 0) { %>
|
||||
<p>No courses found. Click "Scrape Courses" to load Swedish courses from PDGA.</p>
|
||||
<% if (!courses || courses.length === 0) { %>
|
||||
<p style="text-align: center; color: var(--ink-3); padding: 40px 0;">No courses found. Use "Import from Tjing" or scrape courses from PDGA.</p>
|
||||
<% } else { %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Course Name</th>
|
||||
<th class="mobile-hide">City</th>
|
||||
<th class="mobile-hide">Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% courses.forEach(function(course) {
|
||||
var lastUpdated = new Date(course.last_updated).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
%>
|
||||
<tr id="row-<%= course.id %>" class="expandable-row" onclick="toggleCourseLayouts(<%= course.id %>)">
|
||||
<td>
|
||||
<a href="<%= course.link %>" target="_blank" onclick="event.stopPropagation()"><%= course.name %></a>
|
||||
<div class="mobile-only" style="font-size: 11px; color: #999; margin-top: 2px;"><%= course.city %></div>
|
||||
</td>
|
||||
<td class="mobile-hide"><%= course.city %></td>
|
||||
<td class="mobile-hide"><%= lastUpdated %></td>
|
||||
<td>
|
||||
<i class="fas fa-sync-alt refresh-icon" onclick="scrapeLayouts(<%= course.id %>, '<%= course.name.replace(/'/g, "\\'") %>'); event.stopPropagation();" title="Scrape layouts for this course"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="layouts-<%= course.id %>" class="expanded-content">
|
||||
<td colspan="4">
|
||||
<div class="layouts-container" id="layouts-container-<%= course.id %>">
|
||||
<div class="no-layouts">Click to load layouts...</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%- include('course-cards', { courses: courses, query: locals.query, total: locals.total }) %>
|
||||
<div class="course-grid" data-total-count="<%= courses.length %>">
|
||||
<div class="course-row course-row--header" role="row">
|
||||
<div class="course-header-cell">Course</div>
|
||||
<div class="course-header-cell">City</div>
|
||||
<div class="course-header-cell">Last updated</div>
|
||||
<div class="course-header-cell"></div>
|
||||
</div>
|
||||
<% courses.forEach(function(course) {
|
||||
var layoutCount = course.layoutCount || 0;
|
||||
var activeLayoutCount = course.activeLayoutCount || 0;
|
||||
%>
|
||||
<div class="course-row expandable-row" data-course-id="<%= course.id %>" data-course-name="<%= (course.name || '').toLowerCase() %>" data-course-city="<%= (course.city || '').toLowerCase() %>" onclick="toggleCourseLayouts(<%= course.id %>)">
|
||||
<div class="course-cell">
|
||||
<span class="course-name"><%= course.name %></span>
|
||||
<span class="course-meta">
|
||||
<% if (layoutCount > 0) { %>
|
||||
<% if (activeLayoutCount !== layoutCount) { %>
|
||||
<%= layoutCount %> layouts · <%= activeLayoutCount %> active
|
||||
<% } else { %>
|
||||
<%= layoutCount %> layouts
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
No layouts
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="course-city"><%= course.city || '—' %></div>
|
||||
<div class="course-updated"><%= course.last_updated ? new Date(course.last_updated).toISOString().slice(0,10) : '—' %></div>
|
||||
<div class="course-actions">
|
||||
<button class="icon-btn refresh-icon" onclick="event.stopPropagation(); scrapeLayouts(<%= course.id %>, this)" title="Refresh layouts" aria-label="Refresh layouts">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
<button class="icon-btn icon-chev" onclick="event.stopPropagation(); toggleCourseLayouts(<%= course.id %>)" title="Expand row" aria-label="Expand">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expanded-content" id="course-layouts-<%= course.id %>">
|
||||
<div class="expanded-cell">
|
||||
<div class="loading">Loading layouts…</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<%- include('course-cards', { courses: courses, total: courses.length }) %>
|
||||
<% } %>
|
||||
|
||||
Reference in New Issue
Block a user