4bbf6d9728
- Restore src/scrapers/tjing.js with AbortController timeout (8s), error-object returns, and verbatim GraphQL queries - Add getOrCreateLayout() to src/models/course.js - New /api/tjing/search and /api/tjing/import/:tjingId routes; course-table route now includes layoutCount/activeLayoutCount via LEFT JOIN aggregation - Rewrite courses.ejs: action-card with Find/Import tabs, results bar, HTMX course-table-region with body:refresh trigger - Rewrite course-table.ejs: CSS-grid div structure replacing <table>, lazy-load expanded layouts via JS htmx.ajax - Rewrite course-layouts.ejs: layout-card chips with rating tier colouring, collapsible inactive layouts section - Rewrite courses.js: tab switching, live client-side filter, count display, Tjing search/import using DOM API (no innerHTML with untrusted data) - Rewrite courses.css: full new design system using project tokens
62 lines
2.2 KiB
Plaintext
62 lines
2.2 KiB
Plaintext
<% 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(l) {
|
|
if (l.last_played && new Date(l.last_played) >= oneYearAgo) {
|
|
activeLayouts.push(l);
|
|
} else {
|
|
inactiveLayouts.push(l);
|
|
}
|
|
});
|
|
function ratingTier(r) {
|
|
if (r == null) return null;
|
|
if (r >= 970) return 'green';
|
|
if (r >= 940) return 'amber';
|
|
return 'orange';
|
|
}
|
|
%>
|
|
<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">▾</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>
|
|
<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>
|
|
<% } %>
|
|
<% } %>
|