diff --git a/src/admin/public/style.css b/src/admin/public/style.css index 6b90e8f..0440b52 100644 --- a/src/admin/public/style.css +++ b/src/admin/public/style.css @@ -336,6 +336,79 @@ pre { font-size: 0.8rem; white-space: pre-wrap; word-break: break-all; max-width .form-row input { flex: 1; } +.wallet-layout { + display: grid; + grid-template-columns: 280px 1fr; + gap: 1.5rem; + align-items: start; +} + +@media (max-width: 900px) { + .wallet-layout { grid-template-columns: 1fr; } +} + +.wallet-sidebar { + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1rem; + max-height: 70vh; + overflow-y: auto; +} + +.wallet-sidebar h3 { + margin-bottom: 0.75rem; +} + +.wallet-user-list { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.wallet-user-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border-radius: var(--radius); + text-decoration: none; + color: var(--text); + font-size: 0.85rem; + transition: background 0.15s; +} + +.wallet-user-item:hover { + background: #f0f4ff; +} + +.wallet-user-item.selected { + background: var(--primary); + color: #fff; +} + +.wallet-user-id { + font-weight: 600; + min-width: 28px; +} + +.wallet-user-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.wallet-user-meta { + font-size: 0.75rem; + opacity: 0.7; + white-space: nowrap; +} + +.wallet-main { + min-width: 0; +} + @media (max-width: 640px) { .topnav { flex-direction: column; align-items: flex-start; } .logout-btn { margin-left: 0; } diff --git a/src/admin/routes/seed.js b/src/admin/routes/seed.js index 72f2489..7457171 100644 --- a/src/admin/routes/seed.js +++ b/src/admin/routes/seed.js @@ -59,10 +59,10 @@ router.post('/seed-demo', async (req, res) => { const subSoftware = subs.find(s => s.name === 'Software' && s.category_id === catDigital).id; await db.runAsync(`INSERT INTO users (telegram_id, username, country, city, district, status, total_balance, bonus_balance) VALUES - ('1001', 'alice', 'Russia', 'Moscow', 'Center', 1, 150.00, 25.00), - ('1002', 'bob', 'Russia', 'Moscow', 'Center', 1, 85.50, 10.00), - ('1003', 'charlie', 'Russia', 'Saint Petersburg', 'North', 1, 320.75, 50.00), - ('1004', 'diana', 'Germany', 'Berlin', 'Mitte', 1, 45.00, 5.00), + ('1001', 'alice', 'Russia', 'Moscow', 'Center', 0, 150.00, 25.00), + ('1002', 'bob', 'Russia', 'Moscow', 'Center', 0, 85.50, 10.00), + ('1003', 'charlie', 'Russia', 'Saint Petersburg', 'North', 0, 320.75, 50.00), + ('1004', 'diana', 'Germany', 'Berlin', 'Mitte', 0, 45.00, 5.00), ('1005', 'evan', 'Germany', 'Berlin', 'Mitte', 0, 0.00, 0.00)`); const users = await db.allAsync('SELECT id, username FROM users'); const uAlice = users.find(u => u.username === 'alice').id; diff --git a/src/admin/routes/users.js b/src/admin/routes/users.js index 2349cdb..c49ec1e 100644 --- a/src/admin/routes/users.js +++ b/src/admin/routes/users.js @@ -24,7 +24,7 @@ router.get('/:id', async (req, res) => { router.post('/:id/toggle-status', async (req, res) => { const user = await db.getAsync('SELECT * FROM users WHERE id = ?', [req.params.id]); if (!user) return res.status(404).send('User not found'); - const newStatus = user.status === 1 ? 0 : 1; + const newStatus = user.status === 0 ? 2 : 0; await db.runAsync('UPDATE users SET status = ? WHERE id = ?', [newStatus, user.id]); res.redirect('/users'); }); diff --git a/src/admin/routes/wallets.js b/src/admin/routes/wallets.js index 0b367a1..c82b2a6 100644 --- a/src/admin/routes/wallets.js +++ b/src/admin/routes/wallets.js @@ -1,14 +1,32 @@ import { Router } from 'express'; import db from '../../config/database.js'; -import { renderWalletList } from '../views/wallets.js'; +import { renderWalletLayout } from '../views/wallets.js'; const router = Router(); router.get('/', async (req, res) => { - const wallets = await db.allAsync( - 'SELECT * FROM crypto_wallets ORDER BY id DESC LIMIT 200' + const users = await db.allAsync( + `SELECT u.id, u.telegram_id, u.username, u.status, u.total_balance, u.bonus_balance, + COUNT(w.id) AS wallet_count + FROM users u + LEFT JOIN crypto_wallets w ON w.user_id = u.id AND w.wallet_type NOT LIKE '%#_%' ESCAPE '#' + GROUP BY u.id + ORDER BY u.id DESC` ); - res.send(renderWalletList(wallets)); + + const selectedId = req.query.user ? parseInt(req.query.user, 10) : (users.length > 0 ? users[0].id : null); + + let wallets = []; + let selectedUser = null; + if (selectedId) { + selectedUser = await db.getAsync('SELECT * FROM users WHERE id = ?', [selectedId]); + wallets = await db.allAsync( + `SELECT * FROM crypto_wallets WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#' ORDER BY wallet_type`, + [selectedId] + ); + } + + res.send(renderWalletLayout(users, selectedUser, wallets)); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/admin/views/users.js b/src/admin/views/users.js index 61f9251..ef2bb67 100644 --- a/src/admin/views/users.js +++ b/src/admin/views/users.js @@ -8,12 +8,12 @@ export function renderUserList(users, message) {
Telegram ID: ${user.telegram_id}
Country: ${user.country || '-'}
City: ${user.city || '-'}
-Status: ${user.status === 1 ? 'Active' : 'Banned'}
+Status: ${user.status === 0 ? 'Active' : user.status === 2 ? 'Blocked' : 'Deleted'}
Balance: $${(user.total_balance || 0).toFixed(2)}
Bonus: $${(user.bonus_balance || 0).toFixed(2)}
diff --git a/src/admin/views/wallets.js b/src/admin/views/wallets.js index 6c28e54..27f25c4 100644 --- a/src/admin/views/wallets.js +++ b/src/admin/views/wallets.js @@ -1,17 +1,56 @@ -import { layout, table } from './layout.js'; +import { layout } from './layout.js'; +import { escapeHtml } from './escape.js'; -export function renderWalletList(wallets) { - const headers = ['ID', 'User ID', 'Type', 'Address', 'Balance', 'Created']; - const rows = wallets.map(w => `${(w.address || '').slice(0, 16)}...${escapeHtml((w.address || '').slice(0, 20))}${w.address && w.address.length > 20 ? '...' : ''}Main: $${(selectedUser.total_balance || 0).toFixed(2)}
+Bonus: $${(selectedUser.bonus_balance || 0).toFixed(2)}
+Available: $${((selectedUser.total_balance || 0) + (selectedUser.bonus_balance || 0)).toFixed(2)}
+Status: ${selectedUser.status === 0 ? 'Active' : selectedUser.status === 2 ? 'Blocked' : 'Deleted'}
+| Type | Address | Balance | Created |
|---|
Select a user to view wallets
'} +