feat: Bloomberg Refresh 2024 table style for E-commerce tab
- Added Bloomberg-style table with Follow buttons, ticker IDs, values, and change arrows - Two sections: Revenue by channel + Marketing metrics - SVG arrows (12x10) with fill=currentcolor for up/down/neutral states - Monospace typography for IDs, values, and change amounts - Responsive grid layout (desktop → tablet → mobile) - Follow button toggle with blue active state
This commit is contained in:
1 parent
6d59cf206f
commit
0b49915420
1 file changed
+113
@@ -196,15 +196,61 @@ body {
|
|||||||
.m-fill.yellow { background: var(--yellow); }
|
.m-fill.yellow { background: var(--yellow); }
|
||||||
.m-fill.blue { background: var(--accent); }
|
.m-fill.blue { background: var(--accent); }
|
||||||
|
|
||||||
|
/* BLOOMBERG TABLE (E-commerce) */
|
||||||
|
.b-table-section { display: none; }
|
||||||
|
.b-table-section.active { display: block; }
|
||||||
|
.b-table {
|
||||||
|
width: 100%; border-collapse: collapse; font-size: 13px;
|
||||||
|
background: #0e0e0e; border: 1px solid #1e1e1e; border-radius: 12px; overflow: hidden;
|
||||||
|
}
|
||||||
|
.b-table-header { display: grid; grid-template-columns: 44px 110px 1fr 120px 120px; padding: 10px 18px; border-bottom: 1px solid #1e1e1e; }
|
||||||
|
.b-th { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: #555; }
|
||||||
|
.b-th.right { text-align: right; }
|
||||||
|
.b-row { display: grid; grid-template-columns: 44px 110px 1fr 120px 120px; padding: 10px 18px; transition: background 0.15s; }
|
||||||
|
.b-row:hover { background: #131313; }
|
||||||
|
.b-row + .b-row { border-top: 1px solid #141414; }
|
||||||
|
.b-row:last-child { border-radius: 0 0 11px 11px; }
|
||||||
|
.b-follow { display: flex; align-items: center; justify-content: center; cursor: pointer; }
|
||||||
|
.b-follow-btn {
|
||||||
|
width: 22px; height: 22px; border: 1px solid #2a2a2a; border-radius: 6px;
|
||||||
|
background: transparent; color: #444; font-size: 12px; display: flex;
|
||||||
|
align-items: center; justify-content: center; transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.b-follow-btn:hover { color: #f4f4f4; border-color: #444; background: #1a1a1a; }
|
||||||
|
.b-follow-btn.active { color: #0f6fff; border-color: #0f6fff; background: rgba(15,111,255,0.08); }
|
||||||
|
.b-id { font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: #555; white-space: nowrap; }
|
||||||
|
.b-name { font-size: 13px; color: #d4d4d4; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.b-value { font-family: 'IBM Plex Mono', monospace; font-size: 14px; font-weight: 600; color: #f4f4f4; text-align: right; letter-spacing: -0.3px; }
|
||||||
|
.b-change { display: flex; align-items: center; justify-content: flex-end; gap: 4px; font-family: 'IBM Plex Mono', monospace; font-size: 12px; font-weight: 400; }
|
||||||
|
.b-change.up { color: #16a34a; }
|
||||||
|
.b-change.down { color: #e21d1d; }
|
||||||
|
.b-change.neutral { color: #3b82f6; }
|
||||||
|
.b-arrow { width: 12px; height: 10px; fill: currentColor; flex-shrink: 0; }
|
||||||
|
.b-section-title {
|
||||||
|
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
|
||||||
|
color: #444; padding: 20px 0 8px; margin-top: 8px; border-top: 1px solid #1a1a1a;
|
||||||
|
}
|
||||||
|
.b-section-title:first-child { border-top: none; margin-top: 0; }
|
||||||
|
.b-table-caption {
|
||||||
|
display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.b-table-caption-title { font-size: 14px; font-weight: 600; color: #d4d4d4; }
|
||||||
|
.b-table-caption-sub { font-size: 12px; color: #555; }
|
||||||
|
|
||||||
/* RESPONSIVE */
|
/* RESPONSIVE */
|
||||||
@media(max-width: 1024px) {
|
@media(max-width: 1024px) {
|
||||||
.kpi-grid { grid-template-columns: repeat(3, 1fr); }
|
.kpi-grid { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
.b-table-header, .b-row { grid-template-columns: 44px 80px 1fr 90px 90px; }
|
||||||
}
|
}
|
||||||
@media(max-width: 640px) {
|
@media(max-width: 640px) {
|
||||||
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
|
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
.topbar { padding: 0 16px; }
|
.topbar { padding: 0 16px; }
|
||||||
.page-header, .tabs-section, .charts-wrap { padding-left: 16px; padding-right: 16px; }
|
.page-header, .tabs-section, .charts-wrap { padding-left: 16px; padding-right: 16px; }
|
||||||
.tab-btn { padding: 12px 14px; font-size: 13px; }
|
.tab-btn { padding: 12px 14px; font-size: 13px; }
|
||||||
|
.b-table-header, .b-row { grid-template-columns: 36px 70px 1fr 70px 70px; padding: 8px 12px; }
|
||||||
|
.b-name { font-size: 12px; }
|
||||||
|
.b-value { font-size: 12px; }
|
||||||
|
.b-change { font-size: 11px; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -233,6 +279,26 @@ body {
|
|||||||
|
|
||||||
<div class="kpi-grid" id="kpi-grid"></div>
|
<div class="kpi-grid" id="kpi-grid"></div>
|
||||||
|
|
||||||
|
<!-- Bloomberg-style table (E-commerce only) -->
|
||||||
|
<div class="charts-wrap">
|
||||||
|
<div class="b-table-section active" id="b-table-section">
|
||||||
|
<div class="b-table-caption">
|
||||||
|
<span class="b-table-caption-title">Каналы и метрики</span>
|
||||||
|
<span class="b-table-caption-sub">Обновлено только что</span>
|
||||||
|
</div>
|
||||||
|
<div class="b-table-caption-sub b-section-title">Выручка по каналам</div>
|
||||||
|
<table class="b-table" id="b-table-channels">
|
||||||
|
<thead><tr><th class="b-th"></th><th class="b-th">ID</th><th class="b-th">Канал</th><th class="b-th right">Выручка</th><th class="b-th right">Изменение</th></tr></thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
<div class="b-table-caption-sub b-section-title">Маркетинговые метрики</div>
|
||||||
|
<table class="b-table" id="b-table-marketing">
|
||||||
|
<thead><tr><th class="b-th"></th><th class="b-th">ID</th><th class="b-th">Метрика</th><th class="b-th right">Значение</th><th class="b-th right">Изменение</th></tr></thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="charts-wrap">
|
<div class="charts-wrap">
|
||||||
<div class="charts-grid">
|
<div class="charts-grid">
|
||||||
<!-- Row 1: Main + Side -->
|
<!-- Row 1: Main + Side -->
|
||||||
@@ -388,6 +454,53 @@ function seg(key) {
|
|||||||
}).join('');
|
}).join('');
|
||||||
requestAnimationFrame(() => g.querySelectorAll('.kpi').forEach(c => c.classList.add('visible')));
|
requestAnimationFrame(() => g.querySelectorAll('.kpi').forEach(c => c.classList.add('visible')));
|
||||||
|
|
||||||
|
// Bloomberg table — channels + marketing
|
||||||
|
const arrowUp = '<svg class="b-arrow" viewBox="0 0 24 20"><path fill="currentColor" d="M12 0L24 20L0 20L12 0Z"/></svg>';
|
||||||
|
const arrowDown = '<svg class="b-arrow" viewBox="0 0 24 20"><path fill="currentColor" d="M12 20L0 0L24 0L12 20Z"/></svg>';
|
||||||
|
const arrowNeutral = '<svg class="b-arrow" viewBox="0 0 24 20"><path fill="currentColor" d="M12 0L24 20L0 20L12 0Z"/></svg>';
|
||||||
|
function changeCell(val, t) {
|
||||||
|
const cls = t === 'up' ? 'up' : t === 'down' ? 'down' : 'neutral';
|
||||||
|
const arrow = t === 'up' ? arrowUp : t === 'down' ? arrowDown : arrowNeutral;
|
||||||
|
const sign = t === 'up' ? '+' : t === 'down' ? '−' : '±';
|
||||||
|
return `<div class="b-change ${cls}">${arrow} ${sign}${val}</div>`;
|
||||||
|
}
|
||||||
|
const channels = [
|
||||||
|
{ id:'SEO:CH', name:'SEO-трафик', value:'1.2М ₽', change:'18', t:'up' },
|
||||||
|
{ id:'PPC:CH', name:'Контекстная реклама', value:'980К ₽', change:'12', t:'up' },
|
||||||
|
{ id:'SMM:CH', name:'Соцсети', value:'640К ₽', change:'−3', t:'down' },
|
||||||
|
{ id:'EMAIL:CH',name:'Email-рассылки', value:'420К ₽', change:'8', t:'up' },
|
||||||
|
{ id:'ORG:CH', name:'Органический', value:'380К ₽', change:'2', t:'up' },
|
||||||
|
{ id:'REF:CH', name:'Реферальный', value:'156К ₽', change:'24', t:'up' },
|
||||||
|
{ id:'AFF:CH', name:'Партнёрские', value:'98К ₽', change:'−12', t:'down' },
|
||||||
|
];
|
||||||
|
const marketing = [
|
||||||
|
{ id:'CAC', name:'Стоимость клиента', value:'680 ₽', change:'8', t:'down' },
|
||||||
|
{ id:'AOV', name:'Ср. чек', value:'4 820 ₽', change:'3', t:'up' },
|
||||||
|
{ id:'CVR', name:'Конверсия', value:'3.8%', change:'0.5', t:'up' },
|
||||||
|
{ id:'CLV', name:'Пож. ценность', value:'12 400 ₽', change:'18', t:'up' },
|
||||||
|
{ id:'ROAS', name:'ROAS', value:'4.2×', change:'15', t:'up' },
|
||||||
|
{ id:'Bounce', name:'Показ. отказов', value:'32%', change:'1', t:'down' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function renderTable(body, data) {
|
||||||
|
body.innerHTML = data.map((d, i) => `
|
||||||
|
<tr class="b-row" style="animation-delay:${i*40}ms">
|
||||||
|
<td class="b-follow"><button class="b-follow-btn" title="Follow">+</button></td>
|
||||||
|
<td class="b-id">${d.id}</td>
|
||||||
|
<td class="b-name">${d.name}</td>
|
||||||
|
<td class="b-value">${d.value}</td>
|
||||||
|
<td>${changeCell(d.change, d.t)}</td>
|
||||||
|
</tr>`).join('');
|
||||||
|
// Follow toggle
|
||||||
|
body.querySelectorAll('.b-follow-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => btn.classList.toggle('active'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const tbody1 = document.querySelector('#b-table-channels tbody');
|
||||||
|
const tbody2 = document.querySelector('#b-table-marketing tbody');
|
||||||
|
if (tbody1) renderTable(tbody1, channels);
|
||||||
|
if (tbody2) renderTable(tbody2, marketing);
|
||||||
|
|
||||||
// Sparkline charts
|
// Sparkline charts
|
||||||
const sparkColors = [];
|
const sparkColors = [];
|
||||||
s.kpis.forEach(d => { sparkColors.push(d.t === 'up' ? '#22c55e' : d.t === 'down' ? '#ef4444' : '#3b82f6'); });
|
s.kpis.forEach(d => { sparkColors.push(d.t === 'up' ? '#22c55e' : d.t === 'down' ? '#ef4444' : '#3b82f6'); });
|
||||||
|
|||||||
Reference in new issue
Block a user