winx_prinx-api / client /src /components /OverviewTab.jsx
Sasha
refactor: modularize client App.jsx tab components and update Netlify CSP
fdc7871
Raw
History Blame
6.45 kB
import React from 'react';
import { MessageSquare, Users, Mic, Activity, BarChart2 } from 'lucide-react';
import { Line } from 'react-chartjs-2';
export default function OverviewTab({
statsSummary,
selectedStreamId,
activityData,
isRacing,
startGraphRace,
chartDataConfig,
chartOptions,
voiceWords
}) {
return (
<div className="tab-content">
{/* Stats row */}
<div className="grid-4col" style={{ marginBottom: '2rem' }}>
<div className="stat-card" style={{ '--accent-color': 'var(--color-brand)' }}>
<div className="stat-card-inner">
<div style={{ minWidth: 0, flex: 1, marginRight: '0.5rem' }}>
<div className="stat-label">Сообщений в чате</div>
<div className="stat-value">{statsSummary.messages.toLocaleString()}</div>
</div>
<div className="stat-icon-wrapper">
<MessageSquare size={22} style={{ color: 'var(--color-brand)' }} />
</div>
</div>
</div>
<div className="stat-card" style={{ '--accent-color': 'var(--color-text-accent)' }}>
<div className="stat-card-inner">
<div style={{ minWidth: 0, flex: 1, marginRight: '0.5rem' }}>
<div className="stat-label">Уникальных зрителей</div>
<div className="stat-value">{statsSummary.uniqueChatters.toLocaleString()}</div>
</div>
<div className="stat-icon-wrapper">
<Users size={22} style={{ color: 'var(--color-text-accent)' }} />
</div>
</div>
</div>
<div className="stat-card" style={{ '--accent-color': '#ff007f' }}>
<div className="stat-card-inner">
<div style={{ minWidth: 0, flex: 1, marginRight: '0.5rem' }}>
<div className="stat-label">Самый активный чатер</div>
<div
className="stat-value"
title={statsSummary.mostActiveChatter || ''}
style={{
fontSize: (statsSummary.mostActiveChatter || '').length > 15 ? '1.05rem' : '1.25rem',
letterSpacing: '-0.02em',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
{statsSummary.mostActiveChatter || '—'}
</div>
</div>
<div className="stat-icon-wrapper">
<Users size={22} style={{ color: '#ff007f' }} />
</div>
</div>
</div>
<div className="stat-card" style={{ '--accent-color': '#00f5d4' }}>
<div className="stat-card-inner">
<div style={{ minWidth: 0, flex: 1, marginRight: '0.5rem' }}>
<div className="stat-label">Распознано слов (голос)</div>
<div className="stat-value">{statsSummary.voiceWordsCount.toLocaleString()}</div>
</div>
<div className="stat-icon-wrapper">
<Mic size={22} style={{ color: '#00f5d4' }} />
</div>
</div>
</div>
</div>
<div className="grid-2col" style={{ gridTemplateColumns: '2fr 1fr' }}>
{/* Activity graph */}
<div className="panel">
<div className="panel-header">
<h3 className="panel-title"><Activity size={20} /> Активность чата</h3>
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
<span style={{ fontSize: '0.8rem', color: 'var(--color-text-muted)' }}>выберите конкретный стрим для графика</span>
{activityData.length > 0 && (
<button
className="btn btn-secondary"
style={{ fontSize: '0.75rem', padding: '0.3rem 0.6rem' }}
onClick={startGraphRace}
disabled={isRacing}
>
<Activity size={12} className={isRacing ? "pulse" : ""} /> {isRacing ? 'Гонка идет...' : 'Запустить гонку'}
</button>
)}
</div>
</div>
{selectedStreamId === 'all' ? (
<div className="status-msg" style={{ height: '300px' }}>
<BarChart2 size={36} className="status-msg-icon" />
<p>Для просмотра временного графика выберите конкретный стрим в верхнем меню</p>
</div>
) : activityData.length === 0 ? (
<div className="status-msg" style={{ height: '300px' }}>
<MessageSquare size={36} className="status-msg-icon" />
<p>В этом стриме пока не было сообщений</p>
</div>
) : (
<div className="chart-container">
<Line data={chartDataConfig} options={chartOptions} />
</div>
)}
</div>
{/* Quick Words list */}
<div className="panel">
<div className="panel-header">
<h3 className="panel-title">
<Mic size={18} style={{ color: 'var(--color-text-accent)' }} /> Голос стримера (Топ)
</h3>
</div>
{voiceWords.length === 0 ? (
<div className="status-msg" style={{ height: '300px', padding: '1rem' }}>
<Mic size={28} className="status-msg-icon" />
<p>Слова пока не распознаны. Запустите local_worker на ПК.</p>
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', maxHeight: '320px', overflowY: 'auto', paddingRight: '4px' }}>
{voiceWords.slice(0, 7).map((w, idx) => (
<div key={idx} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0.75rem', backgroundColor: 'var(--color-bg-base)', border: '1px solid var(--color-border)', borderRadius: '6px', alignItems: 'center' }}>
<span style={{ fontWeight: 600, color: 'var(--color-text-main)' }}>#{idx+1} {w.word}</span>
<span className="badge badge-streamer">{w.word_count} раз</span>
</div>
))}
</div>
)}
</div>
</div>
</div>
);
}