111 lines
5.7 KiB
Plaintext
111 lines
5.7 KiB
Plaintext
<%
|
|
function renderSparkline(values, opts) {
|
|
opts = opts || {};
|
|
var w = opts.w || 96;
|
|
var h = opts.h || 28;
|
|
if (!values || values.length < 2) return '';
|
|
var min = Math.min.apply(null, values);
|
|
var max = Math.max.apply(null, values);
|
|
var range = max - min || 1;
|
|
var xStep = w / (values.length - 1);
|
|
|
|
var pts = values.map(function(v, i) {
|
|
return {
|
|
x: (i * xStep).toFixed(1),
|
|
y: (((max - v) / range) * (h - 4) + 2).toFixed(1)
|
|
};
|
|
});
|
|
|
|
var linePath = pts.map(function(p, i) {
|
|
return (i === 0 ? 'M' : 'L') + ' ' + p.x + ' ' + p.y;
|
|
}).join(' ');
|
|
|
|
var last = pts[pts.length - 1];
|
|
var areaPath = linePath + ' L ' + last.x + ' ' + h + ' L 0 ' + h + ' Z';
|
|
|
|
return '<svg width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '" class="spark" aria-hidden="true">' +
|
|
'<path d="' + areaPath + '" style="fill:var(--accent);fill-opacity:0.10"/>' +
|
|
'<path d="' + linePath + '" style="stroke:var(--accent);stroke-width:1.5;fill:none;stroke-linejoin:round;stroke-linecap:round"/>' +
|
|
'<circle cx="' + last.x + '" cy="' + last.y + '" r="2.5" style="fill:var(--accent)"/>' +
|
|
'</svg>';
|
|
}
|
|
%>
|
|
<% if (ratings.length === 0) { %>
|
|
<p style="text-align: center; color: var(--ink-3); padding: 40px 0;">No players tracked yet.</p>
|
|
<% } else { %>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th class="col-rank">#</th>
|
|
<th class="col-player">Player</th>
|
|
<th class="col-rating">Rating<span class="th-hint">+ Δ since last update</span></th>
|
|
<th class="col-predicted">Predicted<span class="th-hint">+ gap from today</span></th>
|
|
<th class="col-actions"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% ratings.forEach(function(player, index) {
|
|
const sparklineSvg = renderSparkline(player.monthlyHistory || []);
|
|
%>
|
|
<tr id="row-<%= player.pdgaNumber %>" class="expandable-row" tabindex="0" onclick="togglePlayerHistory(<%= player.pdgaNumber %>)">
|
|
<td class="col-rank">
|
|
<span class="rank-num mono"><%= index + 1 %></span>
|
|
</td>
|
|
<td class="col-player">
|
|
<div class="cell-name">
|
|
<span class="player-name"><a href="https://www.pdga.com/player/<%= player.pdgaNumber %>" target="_blank" onclick="event.stopPropagation()"><%= player.name %></a></span>
|
|
<span class="pdga-num mono">#<%= player.pdgaNumber %></span>
|
|
</div>
|
|
</td>
|
|
<td class="col-rating cell-rating">
|
|
<% if (player.rating) { %>
|
|
<div class="rating-stack">
|
|
<span class="rating-value rating-big mono" data-rating="<%= player.rating || '' %>" data-stddev="<%= player.stdDev || '' %>" data-pdga="<%= player.pdgaNumber %>"><%= player.rating %></span>
|
|
<%- include('delta-pill', { value: player.ratingChange }) %>
|
|
<% if (sparklineSvg) { %><span class="sparkline"><%- sparklineSvg %></span><% } %>
|
|
</div>
|
|
<% } else { %>
|
|
<span class="rating-pending">Click to load</span>
|
|
<% } %>
|
|
<div class="std-dev-tooltip" id="tooltip-rating-<%= player.pdgaNumber %>"></div>
|
|
</td>
|
|
<td class="col-predicted" id="predicted-<%= player.pdgaNumber %>">
|
|
<% if (player.predictedRating) { %>
|
|
<div class="pred-stack">
|
|
<span class="predicted-value pred-num mono" data-stddev="<%= player.stdDev || '' %>" data-pdga="<%= player.pdgaNumber %>"><%= player.predictedRating %></span>
|
|
<%- include('delta-pill', { value: player.deltaPredicted, extraClass: 'delta-predicted-pill' }) %>
|
|
<% if (player.stdDev) { %><span class="std-dev-inline mono">±<%= player.stdDev %></span><% } %>
|
|
</div>
|
|
<% } else { %>
|
|
<span class="rating-pending">—</span>
|
|
<% } %>
|
|
</td>
|
|
<td class="col-actions cell-actions" onclick="event.stopPropagation()">
|
|
<button class="icon-btn refresh-icon" onclick="refreshPlayerData(<%= player.pdgaNumber %>)" title="Refresh rating + prediction" aria-label="Refresh rating and prediction">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<button class="icon-btn target-rating-icon" onclick="openTargetRatingModal(<%= player.pdgaNumber %>)" title="Calculate target rating" aria-label="Calculate target rating">
|
|
<i class="fas fa-bullseye"></i>
|
|
</button>
|
|
<button class="icon-btn icon-chev" onclick="togglePlayerHistory(<%= player.pdgaNumber %>)" title="Expand row" aria-label="Expand">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<tr id="history-<%= player.pdgaNumber %>" class="expanded-content">
|
|
<td colspan="5" class="expanded-cell">
|
|
<div id="history-content-<%= player.pdgaNumber %>" data-loaded="true">
|
|
<%- include('player-history', {
|
|
pdgaNumber: player.pdgaNumber,
|
|
history: player.ratingHistory || [],
|
|
player: player
|
|
}) %>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<% }); %>
|
|
</tbody>
|
|
</table>
|
|
<%- include('ratings-cards', { ratings: ratings }) %>
|
|
<% } %>
|