- Adiciona widget de cotações ao vivo (USD/BRL e EUR/BRL) com design estilo terminal de trading (dark theme, tipografia mono, glow effects) - Proxy server-side /api/cotacao com cache 3s e token AwesomeAPI - Auto-refresh a cada 3 segundos apenas quando a página está aberta - Corrige cálculo de spread negativo: remove Math.abs() em USD→BRL e Math.max(0,...) no spread líquido - Corrige seção USD→BRL que não aparecia (filtro status !== 'finalizado') - Corrige valor_reais no fluxo USD→BRL: agora calcula valor * cotação - Adiciona classe CSS spread-negative para destacar spreads negativos - Bandeiras de fluxo (BR/US/EU) nos botões de compra e venda Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
406 lines
9.4 KiB
JavaScript
406 lines
9.4 KiB
JavaScript
/**
|
|
* UI Template - Design System Unificado BI-CCC
|
|
* Header, Footer e estilos compartilhados
|
|
*/
|
|
|
|
// CSS Variables compartilhadas - Cores CambioReal
|
|
const cssVariables = `
|
|
:root {
|
|
--primary: #7600be;
|
|
--primary-light: #9B2DE5;
|
|
--primary-dark: #5A0091;
|
|
--primary-bg: #F5EAFA;
|
|
--admin-accent: #2E7D32;
|
|
--admin-dark: #1B5E20;
|
|
--admin-bg: #E8F5E9;
|
|
--corporate-accent: #7600be;
|
|
--corporate-dark: #5A0091;
|
|
--corporate-bg: #F5EAFA;
|
|
--bg: #F0F2F5;
|
|
--card: #FFFFFF;
|
|
--text: #1A1D23;
|
|
--text-secondary: #5F6368;
|
|
--text-muted: #9AA0A6;
|
|
--border: #E8EAED;
|
|
--green: #1E8E3E;
|
|
--green-bg: #E6F4EA;
|
|
--blue: #1A73E8;
|
|
--blue-bg: #E8F0FE;
|
|
--orange: #E8710A;
|
|
--orange-bg: #FEF3E8;
|
|
--red: #D93025;
|
|
--red-bg: #FDE7E7;
|
|
--purple: #7B1FA2;
|
|
--purple-bg: #F3E5F5;
|
|
}
|
|
`;
|
|
|
|
// CSS do Header unificado
|
|
const headerCSS = `
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
background: var(--bg); color: var(--text); line-height: 1.5;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
/* Header Unificado */
|
|
.app-header {
|
|
background: linear-gradient(135deg, var(--header-color) 0%, var(--header-dark) 100%);
|
|
color: white;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
}
|
|
.app-header.admin {
|
|
--header-color: var(--admin-accent);
|
|
--header-dark: var(--admin-dark);
|
|
}
|
|
.app-header.corporate {
|
|
--header-color: var(--corporate-accent);
|
|
--header-dark: var(--corporate-dark);
|
|
}
|
|
.app-header.agent {
|
|
--header-color: var(--primary);
|
|
--header-dark: var(--primary-dark);
|
|
}
|
|
.header-inner {
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
padding: 0 40px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
height: 64px;
|
|
}
|
|
|
|
.header-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
text-decoration: none;
|
|
color: white;
|
|
}
|
|
.header-brand:hover {
|
|
opacity: 0.95;
|
|
}
|
|
.header-brand .logo {
|
|
height: 36px;
|
|
width: auto;
|
|
background: white;
|
|
padding: 6px 12px;
|
|
border-radius: 8px;
|
|
}
|
|
.header-brand .app-name {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.header-brand .app-name-badge {
|
|
background: rgba(255,255,255,0.2);
|
|
padding: 6px 14px;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.header-brand .app-subtitle {
|
|
font-size: 11px;
|
|
opacity: 0.8;
|
|
font-weight: 500;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.header-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
.header-nav a {
|
|
color: white;
|
|
text-decoration: none;
|
|
padding: 8px 16px;
|
|
border-radius: 6px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
transition: all 0.15s;
|
|
opacity: 0.85;
|
|
}
|
|
.header-nav a:hover {
|
|
background: rgba(255,255,255,0.15);
|
|
opacity: 1;
|
|
}
|
|
.header-nav a.active {
|
|
background: rgba(255,255,255,0.2);
|
|
opacity: 1;
|
|
}
|
|
|
|
.header-user {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.header-user .user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
background: rgba(255,255,255,0.1);
|
|
padding: 6px 14px;
|
|
border-radius: 20px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
}
|
|
.header-user .user-avatar {
|
|
width: 28px;
|
|
height: 28px;
|
|
background: rgba(255,255,255,0.25);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
}
|
|
.header-user .user-role {
|
|
font-size: 10px;
|
|
background: rgba(255,255,255,0.2);
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
text-transform: uppercase;
|
|
font-weight: 700;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.header-user .btn-logout {
|
|
background: rgba(255,255,255,0.15);
|
|
color: white;
|
|
border: 1px solid rgba(255,255,255,0.3);
|
|
padding: 8px 16px;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
font-family: inherit;
|
|
transition: all 0.15s;
|
|
}
|
|
.header-user .btn-logout:hover {
|
|
background: rgba(255,255,255,0.25);
|
|
}
|
|
|
|
/* Footer */
|
|
.app-footer {
|
|
text-align: center;
|
|
padding: 20px;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
border-top: 1px solid var(--border);
|
|
background: var(--card);
|
|
margin-top: auto;
|
|
}
|
|
|
|
/* Container padrão */
|
|
.app-container {
|
|
padding: 28px 40px;
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.header-inner {
|
|
padding: 12px 16px;
|
|
height: auto;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.header-brand {
|
|
gap: 10px;
|
|
}
|
|
.header-brand .logo {
|
|
height: 28px;
|
|
padding: 4px 8px;
|
|
}
|
|
.header-brand .app-name-badge {
|
|
font-size: 12px;
|
|
padding: 5px 10px;
|
|
}
|
|
.header-brand .app-subtitle {
|
|
display: none;
|
|
}
|
|
.header-nav {
|
|
width: 100%;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
}
|
|
.header-nav a {
|
|
font-size: 12px;
|
|
padding: 8px 12px;
|
|
}
|
|
.header-user {
|
|
width: 100%;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
}
|
|
.header-user .user-info {
|
|
font-size: 12px;
|
|
}
|
|
.header-user .avatar {
|
|
width: 32px;
|
|
height: 32px;
|
|
font-size: 11px;
|
|
}
|
|
.header-user .btn-logout {
|
|
font-size: 11px;
|
|
padding: 6px 12px;
|
|
}
|
|
.app-container {
|
|
padding: 16px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.header-inner {
|
|
padding: 10px 12px;
|
|
}
|
|
.header-nav {
|
|
flex-wrap: wrap;
|
|
}
|
|
.header-nav a {
|
|
font-size: 11px;
|
|
padding: 6px 10px;
|
|
}
|
|
.header-user .user-info {
|
|
display: none;
|
|
}
|
|
.app-container {
|
|
padding: 12px;
|
|
}
|
|
}
|
|
`;
|
|
|
|
/**
|
|
* Gera o header HTML
|
|
* @param {Object} options
|
|
* @param {string} options.role - 'admin', 'corporate' ou 'agente'
|
|
* @param {string} options.userName - Nome do usuário
|
|
* @param {string} options.activePage - Página ativa para nav
|
|
* @param {boolean} options.showNav - Mostrar navegação
|
|
*/
|
|
function buildHeader(options = {}) {
|
|
const { role = 'agente', userName = '', activePage = '', showNav = true } = options;
|
|
const isAdmin = role === 'admin';
|
|
const isCorporate = role === 'corporate';
|
|
|
|
// Determine header class: admin (green), corporate (purple), agent (purple)
|
|
let headerClass = 'agent';
|
|
if (isAdmin) headerClass = 'admin';
|
|
else if (isCorporate) headerClass = 'corporate';
|
|
|
|
const initials = userName
|
|
.split(' ')
|
|
.map(n => n[0])
|
|
.slice(0, 2)
|
|
.join('')
|
|
.toUpperCase();
|
|
|
|
// Admin navigation: Corporate Dashboard + Users (admin on the right)
|
|
const adminNav = `
|
|
<nav class="header-nav">
|
|
<a href="/corporate" class="${activePage === 'dashboard' ? 'active' : ''}">Corporate</a>
|
|
<a href="/admin" class="${activePage === 'users' ? 'active' : ''}">Usuarios</a>
|
|
</nav>
|
|
`;
|
|
|
|
// Corporate navigation: Dashboard only
|
|
const corporateNav = `
|
|
<nav class="header-nav">
|
|
<a href="/corporate" class="${activePage === 'dashboard' ? 'active' : ''}">Dashboard</a>
|
|
</nav>
|
|
`;
|
|
|
|
// Agent navigation: Just their dashboard
|
|
const agentNav = `
|
|
<nav class="header-nav">
|
|
<a href="/dashboard" class="active">Meu Dashboard</a>
|
|
</nav>
|
|
`;
|
|
|
|
// Select navigation based on role
|
|
let nav = agentNav;
|
|
if (isAdmin) nav = adminNav;
|
|
else if (isCorporate) nav = corporateNav;
|
|
|
|
// Home URL based on role
|
|
let homeUrl = '/dashboard';
|
|
if (isAdmin) homeUrl = '/corporate'; // Admin home is Corporate Dashboard
|
|
else if (isCorporate) homeUrl = '/corporate';
|
|
|
|
// Role label for display
|
|
let roleLabel = 'Agente';
|
|
if (isAdmin) roleLabel = 'Admin';
|
|
else if (isCorporate) roleLabel = 'Corporate';
|
|
|
|
return `
|
|
<header class="app-header ${headerClass}">
|
|
<div class="header-inner">
|
|
<a href="${homeUrl}" class="header-brand">
|
|
<img src="/public/logo.png" alt="CambioReal" class="logo">
|
|
<div class="app-name">
|
|
<span class="app-name-badge">BI - CCC</span>
|
|
<span class="app-subtitle">Central Command Center</span>
|
|
</div>
|
|
</a>
|
|
${showNav ? nav : ''}
|
|
<div class="header-user">
|
|
<div class="user-info">
|
|
<span class="user-avatar">${initials}</span>
|
|
<span>${userName}</span>
|
|
<span class="user-role">${roleLabel}</span>
|
|
</div>
|
|
<a href="/logout" class="btn-logout">Sair</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Gera o footer HTML
|
|
*/
|
|
function buildFooter() {
|
|
const year = new Date().getFullYear();
|
|
return `
|
|
<footer class="app-footer">
|
|
CambioReal © ${year} — BI - CCC (Central Command Center)
|
|
</footer>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Gera o head HTML com estilos base
|
|
* @param {string} title - Título da página
|
|
* @param {string} additionalCSS - CSS adicional
|
|
* @param {string} scripts - Scripts externos (opcional)
|
|
*/
|
|
function buildHead(title, additionalCSS = '', scripts = '') {
|
|
return `
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>${title} | BI - CCC</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
${scripts}
|
|
<style>
|
|
${cssVariables}
|
|
${headerCSS}
|
|
${additionalCSS}
|
|
</style>
|
|
`;
|
|
}
|
|
|
|
module.exports = {
|
|
buildHeader,
|
|
buildFooter,
|
|
buildHead,
|
|
cssVariables,
|
|
headerCSS
|
|
};
|