fix(admin/wallets): user list styling + full-width Owner Summary

- User list: .list-group-item-action instead of .dropdown-item for proper
  row styling, clear layout with status badge and wallet count
- Owner Summary: moved to separate full-width row outside col-md-9,
  now spans entire content area independent of user/wallet panels
This commit is contained in:
NW
2026-07-06 17:59:22 +01:00
parent d4c476002c
commit 86c4e18752

View File

@@ -1,4 +1,5 @@
<div class="row">
<!-- LEFT: User list -->
<div class="col-md-3">
<div class="panel panel-icon">
<div class="panel-hdr">
@@ -15,10 +16,12 @@
<% var isActive = selectedUser && u.id === selectedUser.id; %>
<% var statusText = u.status === 0 ? '✅' : '❌'; %>
<% var seedsParam = seedsUnlocked ? '&seeds=1' : ''; %>
<a href="/wallets?user=<%= u.id %><%= seedsParam %>" class="dropdown-item <%= isActive ? 'active bg-primary-100' : '' %>" data-id="<%= u.id %>" data-name="<%= (u.username || '').toLowerCase() %>" data-tgid="<%= u.telegram_id %>">
<span class="wallet-user-id">#<%= u.id %></span>
<span class="wallet-user-name"><%= u.username || u.telegram_id %></span>
<span class="wallet-user-meta"><%= statusText %> <%= u.wallet_count %>w</span>
<a href="/wallets?user=<%= u.id %><%= seedsParam %>" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2 px-2 <%= isActive ? 'active' : '' %>" data-id="<%= u.id %>" data-name="<%= (u.username || '').toLowerCase() %>" data-tgid="<%= u.telegram_id %>">
<div class="d-flex flex-column">
<span class="fw-bold">#<%= u.id %> <%= u.username || u.telegram_id %></span>
<span class="text-muted small"><%= u.wallet_count %> wallets</span>
</div>
<span class="badge bg-transparent"><%= statusText %></span>
</a>
<% }); %>
<% } %>
@@ -27,6 +30,8 @@
</div>
</div>
</div>
<!-- RIGHT: Selected user details -->
<div class="col-md-9">
<% if (selectedUser) { %>
<h2 class="mb-3"><%= selectedUser.username || 'User #' + selectedUser.id %>
@@ -74,138 +79,143 @@
<% } else { %>
<p class="text-muted">Select a user to view wallets</p>
<% } %>
</div>
</div>
<% if (hasStats) { %>
<h2 class="mb-3">Owner Summary</h2>
<div class="row mb-4">
<div class="col-md-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Total USD</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2">$<%= Number(stats.totalUsd).toFixed(2) %></div>
</div>
</div>
<!-- Owner Summary: full width, separate row -->
<% if (hasStats) { %>
<h2 class="mb-3 mt-4">Owner Summary</h2>
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Total USD</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2">$<%= Number(stats.totalUsd).toFixed(2) %></div>
</div>
</div>
<div class="col-md-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Users</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2"><%= stats.totalUsers %></div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Users</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2"><%= stats.totalUsers %></div>
</div>
</div>
<div class="col-md-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Active Wallets</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2"><%= stats.totalWallets %></div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Active Wallets</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2"><%= stats.totalWallets %></div>
</div>
</div>
<div class="col-md-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Commission Due</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2">$<%= Number(stats.commissionDue).toFixed(2) %></div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Commission Due</h2>
</div>
<div class="panel-container show">
<div class="panel-content text-center">
<div class="fw-bold fs-2">$<%= Number(stats.commissionDue).toFixed(2) %></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Wallet Balances by Currency</h2>
</div>
<div class="panel-container show">
<div class="panel-content">
<table class="table table-sm table-striped table-bordered mb-0">
<thead><tr><th>Coin</th><th>Wallets</th><th>Balance</th><th>USD Value</th></tr></thead>
<tbody>
<% Object.entries(stats.totals).forEach(function(entry) { %>
<% var coin = entry[0]; var balance = entry[1]; %>
<% if (balance <= 0 && !(stats.walletCounts[coin] > 0)) return; %>
<tr>
<td><strong><%= coin.toUpperCase() %></strong></td>
<td><%= stats.walletCounts[coin] || 0 %></td>
<td><%= Number(balance).toFixed(8).replace(/0+$/, '').replace(/\.$/, '.0') %></td>
<td>$<%= Number(stats.usdValues[coin] || 0).toFixed(2) %></td>
</tr>
<% }); %>
<tr class="table-active">
<td><strong>Total</strong></td>
<td><%= stats.totalWallets %></td>
<td></td>
<td><strong>$<%= Number(stats.totalUsd).toFixed(2) %></strong></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Commission <%= stats.commissionEnabled ? '' : '(Disabled)' %></h2>
</div>
<div class="panel-container show">
<div class="panel-content">
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr><td>Rate</td><td><strong><%= stats.commissionRate %>%</strong> of total wallet balances</td></tr>
<tr><td>Total Balances</td><td>$<%= Number(stats.totalUsd).toFixed(2) %></td></tr>
<tr><td>Full Commission</td><td>$<%= Number(stats.currentCommission).toFixed(2) %></td></tr>
<tr><td>Last Paid</td><td>$<%= Number(stats.lastPaidAmount).toFixed(2) %></td></tr>
<tr class="table-warning"><td><strong>Due Now</strong></td><td><strong>$<%= Number(stats.commissionDue).toFixed(2) %></strong></td></tr>
</tbody>
</table>
<p class="text-muted mt-2">Commission is <%= stats.commissionRate %>% of total wallet balances. Each payment records a snapshot &mdash; if the shop continues running and balances grow, the difference becomes the next payment due.</p>
<form method="POST" action="/wallets/record-payment" class="row g-2 mt-2 align-items-end">
<div class="col-auto">
<input name="paid_amount" type="number" step="0.01" class="form-control" placeholder="Amount paid (USD)" required>
</div>
<div class="col-auto">
<input name="note" type="text" class="form-control" placeholder="Note (optional)">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Record Payment</button>
</div>
</form>
<div class="mt-2">
<strong>Pay to:</strong>
<% Object.entries(stats.commissionWallets).forEach(function(entry) { %>
<% if (!entry[1]) return; %>
<div class="commission-wallet"><strong><%= entry[0] %>:</strong> <code class="wallet-addr" data-addr="<%= entry[1] %>" data-bs-toggle="tooltip" title="Click to copy"><%= entry[1] %></code></div>
<% }); %>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Wallet Balances by Currency</h2>
</div>
<div class="panel-container show">
<div class="panel-content">
<table class="table table-sm table-striped table-bordered mb-0">
<thead><tr><th>Coin</th><th>Wallets</th><th>Balance</th><th>USD Value</th></tr></thead>
<tbody>
<% Object.entries(stats.totals).forEach(function(entry) { %>
<% var coin = entry[0]; var balance = entry[1]; %>
<% if (balance <= 0 && !(stats.walletCounts[coin] > 0)) return; %>
<tr>
<td><strong><%= coin.toUpperCase() %></strong></td>
<td><%= stats.walletCounts[coin] || 0 %></td>
<td><%= Number(balance).toFixed(8).replace(/0+$/, '').replace(/\.$/, '.0') %></td>
<td>$<%= Number(stats.usdValues[coin] || 0).toFixed(2) %></td>
</tr>
<% }); %>
<tr class="table-active">
<td><strong>Total</strong></td>
<td><%= stats.totalWallets %></td>
<td></td>
<td><strong>$<%= Number(stats.totalUsd).toFixed(2) %></strong></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Commission <%= stats.commissionEnabled ? '' : '(Disabled)' %></h2>
</div>
<div class="panel-container show">
<div class="panel-content">
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr><td>Rate</td><td><strong><%= stats.commissionRate %>%</strong> of total wallet balances</td></tr>
<tr><td>Total Balances</td><td>$<%= Number(stats.totalUsd).toFixed(2) %></td></tr>
<tr><td>Full Commission</td><td>$<%= Number(stats.currentCommission).toFixed(2) %></td></tr>
<tr><td>Last Paid</td><td>$<%= Number(stats.lastPaidAmount).toFixed(2) %></td></tr>
<tr class="table-warning"><td><strong>Due Now</strong></td><td><strong>$<%= Number(stats.commissionDue).toFixed(2) %></strong></td></tr>
</tbody>
</table>
<p class="text-muted mt-2">Commission is <%= stats.commissionRate %>% of total wallet balances. Each payment records a snapshot &mdash; if the shop continues running and balances grow, the difference becomes the next payment due.</p>
<form method="POST" action="/wallets/record-payment" class="row g-2 mt-2 align-items-end">
<div class="col-auto">
<input name="paid_amount" type="number" step="0.01" class="form-control" placeholder="Amount paid (USD)" required>
</div>
<div class="col-auto">
<input name="note" type="text" class="form-control" placeholder="Note (optional)">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Record Payment</button>
</div>
</form>
<div class="mt-2">
<strong>Pay to:</strong>
<% Object.entries(stats.commissionWallets).forEach(function(entry) { %>
<% if (!entry[1]) return; %>
<div class="commission-wallet"><strong><%= entry[0] %>:</strong> <code class="wallet-addr" data-addr="<%= entry[1] %>" data-bs-toggle="tooltip" title="Click to copy"><%= entry[1] %></code></div>
<% }); %>
</div>
</div>
</div>
</div>
</div>
</div>
<% if (stats.payments && stats.payments.length > 0) { %>
<% if (stats.payments && stats.payments.length > 0) { %>
<div class="row">
<div class="col-12 mb-4">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Payment History</h2>
@@ -232,8 +242,12 @@
</div>
</div>
</div>
<% } %>
</div>
</div>
<% } %>
<div class="row">
<div class="col-12 mb-4">
<div class="panel panel-icon">
<div class="panel-hdr">
<h2>Seed Phrases &amp; Wallet Access</h2>
@@ -264,14 +278,14 @@
</div>
</div>
</div>
<% } %>
</div>
</div>
</div>
<% } %>
<script>
(function() {
var search = document.getElementById('user-search');
var items = document.querySelectorAll('.wallet-user-item, .dropdown-item');
var items = document.querySelectorAll('#user-list .list-group-item');
search.addEventListener('input', function() {
var q = this.value.toLowerCase().trim();
items.forEach(function(item) {