const { useState, useEffect, useMemo } = React;
const {
ResponsiveContainer, AreaChart, Area, BarChart, Bar,
XAxis, YAxis, Tooltip, CartesianGrid, Cell
} = Recharts;
/* ═══════════════════════════════════════════════════════════════
STATS TAB — Tam Yeniden Tasarım
═══════════════════════════════════════════════════════════════ */
/* ── Mini KPI Kartı ── */
function KpiCard({ icon, label, value, sub, color, trend }) {
const trendUp = trend > 0;
const trendColor = trendUp ? '#22c55e' : trend < 0 ? '#ef4444' : 'var(--text-dim)';
return (
{icon}
{trend !== undefined && trend !== null && (
{trendUp ? '↑' : trend < 0 ? '↓' : '→'}
{Math.abs(trend)}%
)}
{sub && !trend && trend !== 0 &&
{sub}
}
);
}
/* ── Saatlik Isı Haritası ── */
function HourlyHeatmap({ data }) {
const maxCount = Math.max(...data.map(d =>d.count), 1);
return (
{data.map(d => {
const intensity = d.count / maxCount;
const bg = d.count === 0
? 'var(--surface2)'
: `rgba(59, 109, 240, ${0.12 + intensity * 0.78})`;
const textColor = intensity > 0.5 ? '#fff' : 'var(--text-dim)';
return (
{String(d.hour).padStart(2, '0')}
{d.count}
);
})}
);
}
/* ── Durum Çubuğu ── */
function StatusBar({ data, total }) {
const statusConfig = {
ended: { label: 'Tamamlanan', color: '#22c55e' },
transferred: { label: 'Transfer', color: '#06b6d4' },
active: { label: 'Aktif', color: '#f59e0b' },
abandoned: { label: 'Terk Edilen', color: '#ef4444' },
failed: { label: 'Başarısız', color: '#ef4444' },
};
if (!data || data.length === 0 || total === 0) return null;
return (
{data.map(d => {
const cfg = statusConfig[d.status] || { label: d.status, color: '#94a3b8' };
const pct = (d.count / total * 100);
if (pct < 1) return null;
return (
3 ? 'auto' : 4,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 11, fontWeight:600, color: '#fff',
transition: 'width .5s ease',
}}>
{pct > 10 && `${Math.round(pct)}%`}
);
})}
{data.map(d => {
const cfg = statusConfig[d.status] || { label: d.status, color: '#94a3b8' };
return (
{cfg.label}
{d.count}
);
})}
);
}
/* ── Özel Tooltip ── */
function ChartTooltip({ active, payload, label, suffix }) {
if (!active || !payload?.length) return null;
return (
{label}
{payload.map((p, i) => (
{p.value} {suffix || 'çağrı'}
))}
);
}
/* ═══ ANA COMPONENT ═══ */
function StatsTab() {
const { data: stats, loading } = window.useApi('/api/stats', []);
const { data: qcSessionsData } = window.useApi('/api/qc/sessions?limit=100', []);
const [period, setPeriod] = useState('14d');
const daily = useMemo(() => {
if (!stats?.daily_calls) return [];
const days = period === '7d' ? 7 : 14;
return stats.daily_calls.slice(-days).map(d => ({
...d,
label: d.date.slice(5),
}));
}, [stats, period]);
const qcStats = useMemo(() => {
// API artık {total, items, ...} formatında döndürüyor
const qcSessions = Array.isArray(qcSessionsData) ? qcSessionsData : (qcSessionsData?.items || []);
if (!qcSessions || qcSessions.length === 0) return null;
const total = qcSessions.length;
const completed = qcSessions.filter(s => s.status === 'completed').length;
const failed = qcSessions.filter(s => s.status === 'failed').length;
const abandoned = qcSessions.filter(s => s.status === 'abandoned').length;
return { total, completed, failed, abandoned, rate: total > 0 ? Math.round(completed / total * 100) : 0 };
}, [qcSessionsData]);
if (loading || !stats) {
return (
);
}
const durBucketColors = ['#e2e8f0', '#bfdbfe', '#93c5fd', '#60a5fa', '#3b82f6', '#1d4ed8'];
return (
{/* ═══ BAŞLIK ═══ */}
Raporlar & Analiz
Çağrı merkezi performansınızın detaylı görünümü
{[{ k: '7d', l: '7 Gün' }, { k: '14d', l: '14 Gün' }].map(p => (
))}
{/* ═══ KPI KARTLARI ═══ */}
}
label="Toplam Çağrı" value={stats.total_calls} color="#3b6df0"
trend={stats.week_change}
/>
}
label="Bugün" value={stats.today_calls} color="#06b6d4"
sub={`Dün: ${stats.yesterday_calls}`}
/>
}
label="Ort. Süre" value={window.fmtDuration(stats.avg_duration)} color="#f59e0b"
sub={`Max: ${window.fmtDuration(stats.max_duration)}`}
/>
}
label="Transfer Oranı"
value={stats.total_calls > 0 ? `%${Math.round(stats.transferred_calls / stats.total_calls * 100)}` : '%0'}
color="#8b5cf6"
sub={`${stats.transferred_calls} çağrı`}
/>
{qcStats && (
}
label="QC Başarı" value={`%${qcStats.rate}`} color="#22c55e"
sub={`${qcStats.completed}/${qcStats.total} tamamlandı`}
/>
)}
{/* ═══ ANA GRAFİKLER ═══ */}
{/* Çağrı Trendi */}
Çağrı Trendi
{stats.this_week_calls} bu hafta
{daily.length > 0 ? (
} />
) : (
)}
{/* Durum Dağılımı + Süre */}
Süre Dağılımı
} />
{stats.duration_distribution.map((_, i) => (
|
))}
{/* ═══ İKİNCİ SATIR ═══ */}
{/* Saatlik Dağılım */}
Saatlik Yoğunluk
Son 7 gün
{stats.hourly_distribution ? (
) : (
Veri yok
)}
{/* QC Sonuçları */}
Kalite Kontrol Özeti
{qcStats && {qcStats.total} değerlendirme}
{qcStats ? (
{[
{ label: 'Başarılı', value: qcStats.completed, color: '#22c55e' },
{ label: 'Başarısız', value: qcStats.failed, color: '#ef4444' },
{ label: 'Terk Edilen', value: qcStats.abandoned, color: '#f59e0b' },
].map(s => (
))}
{/* Başarı çubuğu */}
Başarı oranı: %{qcStats.rate}
) : (
)}
{/* ═══ AGENT PERFORMANSI ═══ */}
{stats.agent_performance?.length > 0 && (
Asistan Performansı
| Asistan |
Toplam Çağrı |
Tamamlanan |
Transfer |
Ort. Süre |
Dağılım |
{stats.agent_performance.map(a => {
const maxTotal = stats.agent_performance[0]?.total || 1;
const barPct = (a.total / maxTotal * 100);
return (
| {a.name} |
{a.total} |
{a.ended} |
{a.transferred} |
{window.fmtDuration(a.avg_duration)} |
|
);
})}
)}
{/* ═══ DEPARTMAN DAĞILIMI ═══ */}
{stats.department_distribution?.length > 0 && (
Departman Dağılımı
{stats.department_distribution.map((d, i) => {
const colors = { support: '#3b6df0', sales: '#22c55e', appointment: '#8b5cf6' };
const labels = { support: 'Destek', sales: 'Satış', appointment: 'Randevu' };
const color = colors[d.dept] || '#94a3b8';
const pct = stats.total_calls > 0 ? Math.round(d.count / stats.total_calls * 100) : 0;
return (
{labels[d.dept] || d.dept}
{d.count} %{pct}
);
})}
)}
);
}
window.StatsTab = StatsTab;