diff --git a/server.js b/server.js index 6b9326a..47f7130 100644 --- a/server.js +++ b/server.js @@ -11,12 +11,13 @@ const express = require('express'); const session = require('express-session'); const path = require('path'); const { authenticate, requireAuth, requireRole, createAgente, createUser } = require('./src/auth'); -const { fetchTransacoes, fetchAllTransacoes, serialize, fetchDailyStats, fetchKPIs, fetchTrend30Days, fetchTopAgentes, fetchTrendByPeriod, fetchKPIsByPeriod, fetchBIData, fetchRevenueAnalytics } = require('./src/queries'); +const { fetchTransacoes, fetchAllTransacoes, serialize, fetchDailyStats, fetchKPIs, fetchTrend30Days, fetchTopAgentes, fetchTrendByPeriod, fetchKPIsByPeriod, fetchBIData, fetchRevenueAnalytics, fetchClientList, fetchClientProfile, fetchClientData } = require('./src/queries'); const { buildHTML } = require('./src/dashboard'); const { buildAdminHTML } = require('./src/admin-panel'); const { buildAdminHomeHTML } = require('./src/admin-home'); const { buildAdminDashboardHTML } = require('./src/admin-dashboard'); const { buildAdminBIHTML } = require('./src/admin-bi'); +const { buildAdminClienteHTML } = require('./src/admin-cliente'); const bcrypt = require('bcrypt'); const db = require('./src/db-local'); const cache = require('./src/cache'); @@ -369,6 +370,54 @@ app.get('/admin/api/bi/revenue', requireRole('admin'), async (req, res) => { } }); +// --- Admin Cliente Dashboard (admin only) --- +app.get('/admin/cliente', requireRole('admin'), (req, res) => { + try { + res.set('Cache-Control', 'no-store, no-cache, must-revalidate'); + res.set('Pragma', 'no-cache'); + const html = buildAdminClienteHTML(req.session.user); + res.send(html); + } catch (err) { + console.error('Admin Cliente error:', err); + res.status(500).send('Erro ao carregar pagina de cliente: ' + err.message); + } +}); + +app.get('/admin/api/clientes', requireRole('admin'), async (req, res) => { + try { + const data = await cache.getOrFetch('client-list', fetchClientList, 15 * 60 * 1000); + res.json(data); + } catch (err) { + console.error('Client list API error:', err); + res.status(500).json({ error: err.message }); + } +}); + +app.get('/admin/api/cliente/:id/profile', requireRole('admin'), async (req, res) => { + try { + const clienteId = parseInt(req.params.id); + if (!clienteId) return res.status(400).json({ error: 'Invalid client ID' }); + const data = await fetchClientProfile(clienteId); + res.json(data); + } catch (err) { + console.error('Client profile API error:', err); + res.status(500).json({ error: err.message }); + } +}); + +app.get('/admin/api/cliente/:id/data', requireRole('admin'), async (req, res) => { + try { + const clienteId = parseInt(req.params.id); + const { start, end } = req.query; + if (!clienteId || !start || !end) return res.status(400).json({ error: 'client ID, start and end required' }); + const data = await fetchClientData(clienteId, start, end); + res.json(data); + } catch (err) { + console.error('Client data API error:', err); + res.status(500).json({ error: err.message }); + } +}); + // Create user (admin only) app.post('/admin/agentes', requireRole('admin'), async (req, res) => { const { nome, email, agente_id, senha, role } = req.body; diff --git a/src/admin-cliente.js b/src/admin-cliente.js new file mode 100644 index 0000000..6d51010 --- /dev/null +++ b/src/admin-cliente.js @@ -0,0 +1,1276 @@ +/** + * Admin Cliente Dashboard - Visao 360 por Cliente + * Admin-only: profile, KPIs, timeline, flow analysis, transactions, behavioral insights + */ +const { buildHeader, buildFooter, buildHead, getChartJsScript } = require('./ui-template'); + +function buildAdminClienteHTML(user) { + const role = user.role || 'admin'; + const pageScripts = getChartJsScript(); + + const now = new Date(); + const today = now.toISOString().slice(0, 10); + const thirtyDaysAgo = new Date(now.getTime() - 30 * 86400000).toISOString().slice(0, 10); + + const pageCSS = ` + html { scroll-behavior: smooth; scroll-padding-top: 20px; } + + /* === TRADING CONSOLE: Light Mode === */ + body.trading-console { + --tc-accent: #1E8E3E; + --tc-accent-bg: rgba(30,142,62,0.08); + --tc-accent-border: rgba(30,142,62,0.15); + --tc-glass: rgba(255,255,255,0.85); + --tc-grid: rgba(0,0,0,0.06); + background: var(--bg); color: var(--text); + } + + /* === TRADING CONSOLE: Dark Mode === */ + [data-theme="dark"] body.trading-console { + --bg: #0D1117; --card: #131A24; --text: #E2E8F0; + --text-secondary: #94A3B8; --text-muted: #64748B; + --border: rgba(0,255,136,0.1); + --green: #00FF88; --green-bg: rgba(0,255,136,0.08); + --blue: #58A6FF; --blue-bg: rgba(88,166,255,0.08); + --orange: #F0883E; --orange-bg: rgba(240,136,62,0.08); + --red: #FF4444; --red-bg: rgba(255,68,68,0.08); + --purple: #BC8CFF; --purple-bg: rgba(188,140,255,0.08); + --admin-accent: #00FF88; --admin-bg: rgba(0,255,136,0.05); + --tc-accent: #00FF88; --tc-accent-bg: rgba(0,255,136,0.08); + --tc-accent-border: rgba(0,255,136,0.15); + --tc-glass: rgba(15,25,35,0.92); --tc-grid: rgba(0,255,136,0.06); + background: #0A0F18 !important; color: var(--text); color-scheme: dark; + } + + /* Console Cards */ + body.trading-console .hero-card, + body.trading-console .chart-card, + body.trading-console .metric-card, + body.trading-console .filter-bar, + body.trading-console .profile-card { + background: var(--card); border: 1px solid var(--border); box-shadow: 0 2px 8px rgba(0,0,0,0.06); + } + [data-theme="dark"] body.trading-console .hero-card, + [data-theme="dark"] body.trading-console .chart-card, + [data-theme="dark"] body.trading-console .metric-card, + [data-theme="dark"] body.trading-console .filter-bar, + [data-theme="dark"] body.trading-console .profile-card { + background: rgba(255,255,255,0.03); border: 1px solid rgba(0,255,136,0.1); + box-shadow: 0 2px 12px rgba(0,0,0,0.3), inset 0 1px 0 rgba(0,255,136,0.05); + } + + /* Console Values */ + body.trading-console .hero-value { font-variant-numeric: tabular-nums; } + [data-theme="dark"] body.trading-console .hero-value { + font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; + text-shadow: 0 0 8px rgba(226,232,240,0.15); + } + + /* Console Section Titles */ + body.trading-console .section-title { color: var(--text-secondary); letter-spacing: 1px; } + body.trading-console .section-title .icon { background: var(--tc-accent-bg) !important; color: var(--tc-accent) !important; } + [data-theme="dark"] body.trading-console .section-title { + color: rgba(0,255,136,0.7); font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; letter-spacing: 2px; + } + [data-theme="dark"] body.trading-console .section-title .icon { background: rgba(0,255,136,0.08) !important; color: #00FF88 !important; } + + /* Console Tables */ + body.trading-console .data-table th { background: var(--bg); color: var(--text-muted); } + [data-theme="dark"] body.trading-console .data-table th { + background: rgba(0,255,136,0.03); color: rgba(0,255,136,0.6); + font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; + } + [data-theme="dark"] body.trading-console .data-table td { + font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 12px; + border-bottom-color: rgba(0,255,136,0.06); + } + [data-theme="dark"] body.trading-console .data-table tr:hover td { background: rgba(0,255,136,0.05); } + + /* Console Buttons Dark */ + [data-theme="dark"] body.trading-console .preset-btn { background: rgba(255,255,255,0.03); color: var(--text-secondary); border-color: rgba(0,255,136,0.1); } + [data-theme="dark"] body.trading-console .preset-btn:hover { border-color: #00FF88; color: #00FF88; } + [data-theme="dark"] body.trading-console .preset-btn.active { background: rgba(0,255,136,0.15); color: #00FF88; border-color: rgba(0,255,136,0.3); } + [data-theme="dark"] body.trading-console .gran-btn { background: rgba(255,255,255,0.03); color: var(--text-secondary); border-color: rgba(0,255,136,0.1); } + [data-theme="dark"] body.trading-console .gran-btn:hover { border-color: #F9A825; color: #F9A825; } + [data-theme="dark"] body.trading-console .gran-btn.active { background: rgba(249,168,37,0.15); color: #F9A825; border-color: rgba(249,168,37,0.3); } + [data-theme="dark"] body.trading-console .date-inputs input[type="date"] { background: rgba(255,255,255,0.03); color: var(--text); border-color: rgba(0,255,136,0.1); } + [data-theme="dark"] body.trading-console .app-footer { background: #0A0F18; border-top-color: rgba(0,255,136,0.1); color: var(--text-muted); } + + /* Scrollbars Dark */ + [data-theme="dark"] body.trading-console ::-webkit-scrollbar { width: 6px; height: 6px; } + [data-theme="dark"] body.trading-console ::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); } + [data-theme="dark"] body.trading-console ::-webkit-scrollbar-thumb { background: rgba(0,255,136,0.2); border-radius: 3px; } + [data-theme="dark"] body.trading-console ::-webkit-scrollbar-thumb:hover { background: rgba(0,255,136,0.35); } + + /* === Floating Console Nav === */ + .console-nav { + position: fixed; right: 0; top: 50%; transform: translateY(-50%); + z-index: 1000; display: flex; flex-direction: column; gap: 0; padding: 0; background: none; + } + .console-nav-btn { + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 6px; width: 38px; padding: 14px 0; + background: var(--card); border: 1px solid var(--border); border-right: none; + border-radius: 8px 0 0 8px; margin-bottom: -1px; + color: var(--text-muted); font-size: 11px; font-weight: 600; + cursor: pointer; transition: all 0.2s ease; text-decoration: none; position: relative; + box-shadow: -2px 0 6px rgba(0,0,0,0.04); + } + .console-nav-btn:hover { color: var(--text); background: var(--bg); width: 42px; } + .console-nav-btn.active { color: var(--tc-accent); background: var(--bg); width: 44px; border-color: var(--tc-accent); border-right: 1px solid var(--bg); z-index: 2; box-shadow: -3px 0 10px rgba(0,0,0,0.08); } + [data-theme="dark"] .console-nav-btn { background: #161B22; border-color: rgba(0,255,136,0.1); color: rgba(255,255,255,0.35); box-shadow: -2px 0 8px rgba(0,0,0,0.3); } + [data-theme="dark"] .console-nav-btn:hover { background: #1A2332; color: rgba(255,255,255,0.7); } + [data-theme="dark"] .console-nav-btn.active { background: #0D1117; color: #00FF88; border-color: rgba(0,255,136,0.3); border-right-color: #0D1117; box-shadow: -3px 0 12px rgba(0,0,0,0.4); text-shadow: 0 0 8px rgba(0,255,136,0.4); } + .console-nav-btn .nav-icon { font-size: 15px; line-height: 1; flex-shrink: 0; } + .console-nav-btn .nav-label { writing-mode: vertical-rl; text-orientation: mixed; font-size: 9px; letter-spacing: 1px; text-transform: uppercase; font-weight: 700; white-space: nowrap; } + [data-theme="dark"] .console-nav-btn .nav-label { font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; } + + @media (min-width: 769px) { body.trading-console .app-container { padding-right: 50px; } } + @media (max-width: 900px) { .console-nav-btn { width: 34px; padding: 10px 0; } .console-nav-btn .nav-label { font-size: 8px; } .console-nav-btn:hover { width: 38px; } .console-nav-btn.active { width: 40px; } } + @media (max-width: 768px) { .console-nav-btn .nav-label { display: none; } .console-nav-btn { width: 36px; padding: 12px 0; } .console-nav-btn .nav-icon { font-size: 16px; } .console-nav-btn:hover { width: 40px; } .console-nav-btn.active { width: 42px; } } + @media (max-width: 480px) { .console-nav-btn { width: 30px; padding: 10px 0; } .console-nav-btn .nav-icon { font-size: 13px; } .console-nav-btn:hover { width: 34px; } .console-nav-btn.active { width: 36px; } } + + /* === Client Search === */ + .client-search-wrap { + position: relative; max-width: 600px; margin: 0 auto 24px; + } + .client-search-input { + width: 100%; padding: 14px 20px; border: 2px solid var(--border); border-radius: 12px; + font-size: 15px; font-family: inherit; background: var(--card); color: var(--text); + transition: border-color 0.2s; outline: none; + } + .client-search-input:focus { border-color: var(--tc-accent); } + .client-search-input::placeholder { color: var(--text-muted); } + [data-theme="dark"] .client-search-input { background: rgba(255,255,255,0.03); border-color: rgba(0,255,136,0.15); } + [data-theme="dark"] .client-search-input:focus { border-color: #00FF88; } + + .client-dropdown { + position: absolute; top: 100%; left: 0; right: 0; z-index: 100; + background: var(--card); border: 1px solid var(--border); border-radius: 12px; + box-shadow: 0 8px 32px rgba(0,0,0,0.15); max-height: 360px; overflow-y: auto; + display: none; margin-top: 4px; + } + .client-dropdown.open { display: block; } + [data-theme="dark"] .client-dropdown { background: #161B22; border-color: rgba(0,255,136,0.15); box-shadow: 0 8px 32px rgba(0,0,0,0.5); } + + .client-dropdown-item { + padding: 12px 20px; cursor: pointer; font-size: 14px; color: var(--text); + border-bottom: 1px solid var(--border); transition: background 0.1s; + } + .client-dropdown-item:last-child { border-bottom: none; } + .client-dropdown-item:hover { background: var(--bg); } + [data-theme="dark"] .client-dropdown-item:hover { background: rgba(0,255,136,0.05); } + .client-dropdown-item .item-id { font-size: 11px; color: var(--text-muted); margin-left: 8px; } + + .client-selected-badge { + display: none; align-items: center; gap: 12px; + padding: 12px 20px; background: var(--tc-accent-bg); border: 1px solid var(--tc-accent-border); + border-radius: 12px; font-size: 15px; font-weight: 600; color: var(--text); + max-width: 600px; margin: 0 auto 24px; + } + .client-selected-badge.visible { display: flex; } + .client-selected-badge .badge-name { flex: 1; } + .client-selected-badge .badge-clear { + background: none; border: 1px solid var(--border); border-radius: 8px; + padding: 6px 14px; font-size: 12px; font-weight: 600; cursor: pointer; + color: var(--text-secondary); font-family: inherit; transition: all 0.15s; + } + .client-selected-badge .badge-clear:hover { border-color: var(--red); color: var(--red); } + + /* === Empty State === */ + .empty-state { + text-align: center; padding: 80px 20px; color: var(--text-muted); + } + .empty-state .empty-icon { font-size: 64px; margin-bottom: 16px; opacity: 0.3; } + .empty-state h2 { font-size: 20px; font-weight: 700; margin-bottom: 8px; color: var(--text-secondary); } + .empty-state p { font-size: 14px; } + + /* === Profile Card === */ + .profile-card { + border-radius: 16px; padding: 24px; margin-bottom: 24px; + display: flex; align-items: center; gap: 24px; flex-wrap: wrap; + } + .profile-avatar { + width: 56px; height: 56px; border-radius: 50%; + background: var(--tc-accent-bg); color: var(--tc-accent); + display: flex; align-items: center; justify-content: center; + font-size: 20px; font-weight: 800; flex-shrink: 0; + } + .profile-info { flex: 1; min-width: 200px; } + .profile-name { font-size: 20px; font-weight: 800; color: var(--text); } + .profile-id { font-size: 12px; color: var(--text-muted); } + .profile-stats { + display: flex; gap: 24px; flex-wrap: wrap; + } + .profile-stat { + text-align: center; min-width: 80px; + } + .profile-stat-label { font-size: 10px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; } + .profile-stat-value { font-size: 16px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; } + [data-theme="dark"] .profile-stat-value { font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; } + + /* === Filter Bar === */ + .filter-bar { + background: var(--card); border-radius: 16px; padding: 20px 24px; + border: 1px solid var(--border); box-shadow: 0 2px 8px rgba(0,0,0,0.04); + margin-bottom: 24px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; + } + .filter-bar-label { font-size: 13px; font-weight: 600; color: var(--text-secondary); } + .filter-presets { display: flex; gap: 8px; } + .preset-btn { + padding: 8px 16px; border: 1px solid var(--border); border-radius: 8px; + background: var(--bg); font-size: 13px; font-weight: 600; cursor: pointer; + color: var(--text-secondary); transition: all 0.15s; font-family: inherit; + } + .preset-btn:hover { border-color: var(--admin-accent); color: var(--admin-accent); } + .preset-btn.active { background: var(--admin-accent); color: white; border-color: var(--admin-accent); } + .filter-divider { width: 1px; height: 32px; background: var(--border); } + .date-inputs { display: flex; align-items: center; gap: 8px; } + .date-inputs label { font-size: 12px; font-weight: 600; color: var(--text-muted); } + .date-inputs input[type="date"] { + padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; + font-size: 13px; font-family: inherit; background: var(--bg); color: var(--text); + } + .period-info { margin-left: auto; font-size: 12px; color: var(--text-muted); font-weight: 500; background: var(--bg); padding: 6px 12px; border-radius: 6px; } + + /* === Hero KPIs === */ + .hero-grid { + display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 28px; + } + .hero-card { + background: var(--card); border-radius: 16px; padding: 20px 18px; + border: 1px solid var(--border); box-shadow: 0 2px 8px rgba(0,0,0,0.04); + position: relative; overflow: hidden; min-width: 0; + } + .hero-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; } + .hero-card.spread::before { background: linear-gradient(90deg, var(--green), #4CAF50); } + .hero-card.volume::before { background: linear-gradient(90deg, var(--blue), #42A5F5); } + .hero-card.transactions::before { background: linear-gradient(90deg, var(--purple), #AB47BC); } + .hero-card.ticket::before { background: linear-gradient(90deg, #00897B, #26A69A); } + .hero-card.avgspread::before { background: linear-gradient(90deg, var(--orange), #FFA726); } + .hero-label { font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; } + .hero-value { + font-size: clamp(14px, 1.6vw, 26px); font-weight: 800; color: var(--text); margin-bottom: 4px; + font-variant-numeric: tabular-nums; word-break: break-word; overflow-wrap: break-word; min-width: 0; line-height: 1.2; + } + .hero-badge { display: inline-block; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 12px; } + .hero-badge.up { background: var(--green-bg); color: var(--green); } + .hero-badge.down { background: var(--red-bg); color: var(--red); } + .hero-badge.neutral { background: var(--blue-bg); color: var(--blue); } + .hero-sub { font-size: 12px; color: var(--text-muted); margin-top: 6px; } + + /* === Section Headers === */ + .section-title { + font-size: 14px; font-weight: 700; color: var(--text-secondary); + text-transform: uppercase; letter-spacing: 1px; margin-bottom: 16px; + display: flex; align-items: center; gap: 8px; + } + .section-title .icon { + width: 28px; height: 28px; border-radius: 8px; display: flex; + align-items: center; justify-content: center; font-size: 14px; + } + + /* === Charts === */ + .charts-row { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; margin-bottom: 28px; } + .charts-row.equal { grid-template-columns: 1fr 1fr; } + .charts-row.triple { grid-template-columns: 1fr 1fr 1fr; } + .chart-card { + background: var(--card); border-radius: 16px; padding: 24px; + border: 1px solid var(--border); box-shadow: 0 2px 8px rgba(0,0,0,0.04); + } + .chart-card h3 { + font-size: 14px; font-weight: 700; color: var(--text); margin-bottom: 16px; + display: flex; align-items: center; gap: 8px; + } + .chart-card h3 .badge { font-size: 10px; padding: 3px 8px; border-radius: 10px; font-weight: 700; background: var(--bg); color: var(--text-muted); } + .chart-wrap { position: relative; height: 280px; } + .chart-wrap.short { height: 220px; } + + /* Granularity Buttons */ + .gran-selector { display: flex; align-items: center; gap: 8px; } + .gran-btn { + padding: 6px 14px; border: 1px solid var(--border); border-radius: 8px; + background: var(--bg); font-size: 12px; font-weight: 600; cursor: pointer; + color: var(--text-secondary); transition: all 0.15s; font-family: inherit; + } + .gran-btn:hover { border-color: #F9A825; color: #F9A825; } + .gran-btn.active { background: #F9A825; color: white; border-color: #F9A825; } + + /* === Transaction Table === */ + .data-table { width: 100%; border-collapse: collapse; font-size: 13px; } + .data-table th { + text-align: left; padding: 10px 12px; font-weight: 700; font-size: 11px; + text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); + border-bottom: 2px solid var(--border); cursor: pointer; user-select: none; white-space: nowrap; + } + .data-table th:hover { color: var(--tc-accent); } + .data-table th .sort-arrow { font-size: 10px; margin-left: 4px; opacity: 0.4; } + .data-table th.sorted .sort-arrow { opacity: 1; color: var(--tc-accent); } + .data-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); color: var(--text); font-variant-numeric: tabular-nums; } + .data-table tr:last-child td { border-bottom: none; } + .data-table tr:hover td { background: var(--bg); } + .data-table tfoot td { font-weight: 700; border-top: 2px solid var(--border); background: var(--bg); } + + .flow-tag { + display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 11px; font-weight: 700; + } + .flow-tag.brl-usd { background: var(--blue-bg); color: var(--blue); } + .flow-tag.usd-brl { background: var(--green-bg); color: var(--green); } + + /* === Pagination === */ + .table-controls { + display: flex; align-items: center; justify-content: space-between; gap: 12px; + padding: 12px 0; flex-wrap: wrap; + } + .table-controls select { + padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; + font-size: 12px; font-family: inherit; background: var(--bg); color: var(--text); + } + .pagination { + display: flex; align-items: center; gap: 4px; + } + .page-btn { + padding: 6px 12px; border: 1px solid var(--border); border-radius: 6px; + background: var(--bg); font-size: 12px; font-weight: 600; cursor: pointer; + color: var(--text-secondary); font-family: inherit; transition: all 0.15s; + } + .page-btn:hover { border-color: var(--tc-accent); color: var(--tc-accent); } + .page-btn.active { background: var(--tc-accent); color: white; border-color: var(--tc-accent); } + .page-btn:disabled { opacity: 0.4; cursor: default; } + .page-info { font-size: 12px; color: var(--text-muted); } + + .btn-export { + padding: 8px 16px; border: 1px solid var(--border); border-radius: 8px; + background: var(--bg); font-size: 12px; font-weight: 600; cursor: pointer; + color: var(--text-secondary); font-family: inherit; transition: all 0.15s; + } + .btn-export:hover { border-color: var(--tc-accent); color: var(--tc-accent); } + + /* === Loading === */ + .loading-overlay { + position: absolute; inset: 0; background: rgba(255,255,255,0.8); + display: flex; align-items: center; justify-content: center; + border-radius: 16px; z-index: 10; font-size: 13px; color: var(--text-muted); + } + [data-theme="dark"] .loading-overlay { background: rgba(13,17,23,0.85); } + + .content-area { display: none; } + .content-area.visible { display: block; } + + /* === Dark Mode extra === */ + [data-theme="dark"] .preset-btn { background: var(--card); color: var(--text-secondary); border-color: var(--border); } + [data-theme="dark"] .preset-btn:hover { border-color: var(--admin-accent); color: var(--green); } + [data-theme="dark"] .gran-btn { background: var(--card); color: var(--text-secondary); border-color: var(--border); } + [data-theme="dark"] .date-inputs input[type="date"] { background: var(--card); color: var(--text); border-color: var(--border); } + [data-theme="dark"] .data-table tr:hover td { background: rgba(255,255,255,0.03); } + + /* === Responsive === */ + @media (max-width: 1200px) { + .hero-grid { grid-template-columns: repeat(3, 1fr); } + .hero-value { font-size: clamp(15px, 2.4vw, 22px); } + } + @media (max-width: 900px) { + .charts-row { grid-template-columns: 1fr; } + .charts-row.equal { grid-template-columns: 1fr; } + .charts-row.triple { grid-template-columns: 1fr 1fr; } + .charts-row.triple > :last-child { grid-column: span 2; } + .filter-divider { display: none; } + .filter-bar { gap: 10px; } + .profile-card { flex-direction: column; text-align: center; } + .profile-stats { justify-content: center; } + } + @media (max-width: 768px) { + .filter-bar { padding: 14px 16px; gap: 10px; flex-direction: column; align-items: stretch; } + .filter-bar-label { text-align: center; } + .filter-presets { flex-wrap: wrap; justify-content: center; } + .preset-btn { padding: 10px 14px; min-height: 44px; font-size: 13px; flex: 1; min-width: 60px; text-align: center; } + .date-inputs { flex-wrap: wrap; justify-content: center; gap: 6px; } + .date-inputs input[type="date"] { flex: 1; min-width: 130px; min-height: 44px; } + .period-info { margin-left: 0; width: 100%; text-align: center; } + .hero-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; } + .hero-card { padding: 16px 18px; border-radius: 12px; } + .hero-card:last-child { grid-column: span 2; } + .hero-value { font-size: 22px; } + .hero-label { font-size: 10px; } + .charts-row, .charts-row.equal, .charts-row.triple { grid-template-columns: 1fr; } + .charts-row.triple > :last-child { grid-column: span 1; } + .chart-card { padding: 18px; border-radius: 12px; } + .chart-wrap { height: 250px; } + .section-title { font-size: 13px; margin-bottom: 12px; } + .table-controls { flex-direction: column; align-items: stretch; } + .data-table { font-size: 12px; } + .data-table th { padding: 10px 10px; font-size: 10px; } + .data-table td { padding: 10px 10px; white-space: nowrap; } + } + @media (max-width: 480px) { + .hero-grid { grid-template-columns: 1fr; gap: 10px; } + .hero-card { padding: 14px 16px; } + .hero-card:last-child { grid-column: span 1; } + .hero-value { font-size: 20px; } + .hero-badge { font-size: 10px; padding: 2px 8px; } + .chart-card { padding: 14px; } + .chart-wrap { height: 200px; } + .chart-wrap.short { height: 160px; } + .chart-card h3 { font-size: 13px; } + .data-table { font-size: 11px; } + .data-table th { padding: 8px 6px; font-size: 9px; } + .data-table td { padding: 8px 6px; } + .section-title { font-size: 12px; letter-spacing: 0.5px; } + .section-title .icon { width: 24px; height: 24px; font-size: 12px; } + .profile-stats { gap: 12px; } + .profile-stat-value { font-size: 14px; } + .client-search-input { font-size: 14px; padding: 12px 16px; } + } + `; + + return ` + + +${buildHead('Clientes 360', pageCSS, pageScripts)} + + + +${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })} + +
+ + +
+ +
+
+
+ + +
+ + +
+
🔍
+

Selecione um cliente

+

Use a busca acima para encontrar um cliente e visualizar sua analise completa.

+
+ + +
+ + +
+
--
+
+
--
+
--
+
+
+
+
Primeira Op
+
--
+
+
+
Ultima Op
+
--
+
+
+
Dias Inativo
+
--
+
+
+
Vol. Lifetime
+
--
+
+
+
Total Ops
+
--
+
+
+
Receita
+
--
+
+
+
+ + +
+ Periodo: +
+ + + + + +
+
+
+ + + + +
+ -- +
+ + +
+
+
Volume USD
+
--
+ -- +
+
+
Transacoes
+
--
+ -- +
+
+
Receita Spread
+
--
+ -- +
+
+
Ticket Medio
+
--
+
USD por operacao
+
+
+
Spread Medio
+
--
+
% medio ponderado
+
+
+ + +
+ 📈 + Linha do Tempo +
+
+
+

Volume + Quantidade ao Longo do Tempo

+
+ + + +
+
+
+
+ + +
+ + Analise de Fluxo +
+
+
+

BRL→USD vs USD→BRL

+
+
+
+

Tendencia de Spread % por Fluxo

+
+
+
+ + +
+ 📋 + Historico de Transacoes +
+
+
+
+ -- + +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + +
Data Fluxo USD BRL Taxa PTAX Spread% IOF Status Provider
--
+
+
+ + +
+ 🧠 + Insights Comportamentais +
+
+
+

Atividade por Dia da Semana

+
+
+
+

Ticket Medio Mensal

+
+
+
+

Providers / Metodos

+
+
+
+ +
+ +
+ + + + +${buildFooter()} + +