All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 45s
139 lines
6.3 KiB
Plaintext
139 lines
6.3 KiB
Plaintext
<%- include('../partials/header') %>
|
|
|
|
<div class="container">
|
|
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.5rem;">
|
|
<div>
|
|
<h1 style="margin: 0;"><%= hunt.name %></h1>
|
|
<span style="color: var(--muted); font-family: monospace; font-size: 1rem;"><%= hunt.short_name %></span>
|
|
</div>
|
|
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
<a href="/admin/hunts/<%= hunt.id %>/edit" class="btn btn-outline">✏️ Edit</a>
|
|
<a href="/admin/hunts/<%= hunt.id %>/pdf" class="btn btn-success">📥 Download PDF</a>
|
|
<a href="/hunt/<%= hunt.short_name %>" class="btn btn-outline">View Public Page</a>
|
|
<a href="/hunt/<%= hunt.short_name %>/leaderboard" class="btn btn-outline">Leaderboard</a>
|
|
</div>
|
|
</div>
|
|
|
|
<% if (hunt.description) { %>
|
|
<div class="card">
|
|
<p style="margin: 0; color: var(--muted);"><%= hunt.description %></p>
|
|
</div>
|
|
<% } %>
|
|
|
|
<div class="manage-stats">
|
|
<div class="stats-row manage-stats-row-1">
|
|
<div class="stat-box">
|
|
<div class="value"><%= hunt.package_count %></div>
|
|
<div class="label">Packages</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="value"><%= packages.filter(p => p.scan_count > 0).length %></div>
|
|
<div class="label">Found</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="value"><% if (hunt.start_date) { %><time datetime="<%= hunt.start_date %>"><%= new Date(hunt.start_date).toLocaleDateString() %></time><% } else { %>Now<% } %></div>
|
|
<div class="label">Starts</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="value"><% if (hunt.expiry_date) { %><time datetime="<%= hunt.expiry_date %>"><%= new Date(hunt.expiry_date).toLocaleDateString() %></time><% } else { %>Never<% } %></div>
|
|
<div class="label">Expires</div>
|
|
</div>
|
|
</div>
|
|
<div class="stats-row manage-stats-row-2">
|
|
<div class="stat-box">
|
|
<div class="value"><%= packages.reduce((sum, p) => sum + p.scan_count, 0) %></div>
|
|
<div class="label">Total Scans</div>
|
|
</div>
|
|
<div class="stat-box hide-mobile">
|
|
<div class="value"><%= typeof stats !== 'undefined' ? stats.uniquePlayers : 0 %></div>
|
|
<div class="label">Players</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="value"><%= typeof stats !== 'undefined' ? stats.discoveryRate + '%' : '0%' %></div>
|
|
<div class="label">Discovery Rate</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<% if (typeof stats !== 'undefined' && stats.topFinders.length > 0) { %>
|
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem;">
|
|
<div class="card" style="flex: 1; min-width: 280px;">
|
|
<div class="card-header">Top Finders</div>
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead><tr><th>Player</th><th>Finds</th><th>Points</th></tr></thead>
|
|
<tbody>
|
|
<% stats.topFinders.forEach(f => { %>
|
|
<tr>
|
|
<td><a href="/player/<%= f.username %>"><%= f.display_name %></a></td>
|
|
<td><%= f.finds %></td>
|
|
<td><span class="points-badge">+<%= f.points %></span></td>
|
|
</tr>
|
|
<% }) %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% if (stats.recentScans.length > 0) { %>
|
|
<div class="card" style="flex: 1; min-width: 280px;">
|
|
<div class="card-header">Recent Scans</div>
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead><tr><th>#</th><th>Player</th><th>Points</th><th>When</th></tr></thead>
|
|
<tbody>
|
|
<% stats.recentScans.forEach(s => { %>
|
|
<tr>
|
|
<td><%= s.card_number %></td>
|
|
<td><a href="/player/<%= s.username %>"><%= s.display_name %></a></td>
|
|
<td><span class="points-badge">+<%= s.points_awarded %></span></td>
|
|
<td style="font-size: 0.85rem; color: var(--muted);"><time datetime="<%= s.scanned_at %>"><%= new Date(s.scanned_at).toLocaleString() %></time></td>
|
|
</tr>
|
|
<% }) %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
<% } %>
|
|
|
|
<h2 style="margin-top: 1.5rem; margin-bottom: 1rem;">All Packages</h2>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th class="hide-mobile">#</th>
|
|
<th>Code</th>
|
|
<th class="hide-mobile">Scans</th>
|
|
<th class="hide-mobile">First Scanner</th>
|
|
<th>Last Scanner</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% packages.forEach(pkg => { %>
|
|
<tr>
|
|
<td class="hide-mobile"><strong><%= pkg.card_number %></strong></td>
|
|
<td style="font-family: monospace;"><%= pkg.unique_code %></td>
|
|
<td class="hide-mobile"><%= pkg.scan_count %></td>
|
|
<td class="hide-mobile"><% if (pkg.first_scanner_name) { %><a href="/player/<%= pkg.first_scanner_username %>"><%= pkg.first_scanner_name %></a><% } else { %>---<% } %></td>
|
|
<td><% if (pkg.last_scanner_name) { %><a href="/player/<%= pkg.last_scanner_username %>"><%= pkg.last_scanner_name %></a><% } else { %>---<% } %></td>
|
|
<td>
|
|
<div style="display: flex; gap: 0.4rem; align-items: stretch;">
|
|
<a href="/hunt/<%= hunt.short_name %>/<%= pkg.card_number %>" class="btn btn-sm btn-outline">View</a>
|
|
<button class="btn btn-sm btn-outline" onclick="navigator.clipboard.writeText('<%= baseUrl %>/loot/<%= hunt.short_name %>/<%= pkg.unique_code %>').then(()=>{this.textContent='Copied!';setTimeout(()=>this.textContent='Copy Link',1500)})">Copy Link</button>
|
|
<form method="POST" action="/admin/hunts/<%= hunt.id %>/packages/<%= pkg.id %>/reroll" style="margin:0;">
|
|
<button type="submit" class="btn btn-sm btn-outline" title="Reroll code" onclick="return confirm('Reroll code for package #<%= pkg.card_number %>? The old code will stop working.')">🎲</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<% }) %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<%- include('../partials/footer') %>
|