mobile: responsive admin dashboard

This commit is contained in:
root 2026-05-25 12:57:46 +00:00
parent 79e1173dd3
commit 0d5fb1b79a
1 changed files with 101 additions and 84 deletions

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer,
PieChart, Pie, Cell, Legend, LineChart, Line, CartesianGrid, PieChart, Pie, Cell, Legend, LineChart, Line, CartesianGrid,
@ -8,6 +8,18 @@ import { Badge } from '../UI/Badge';
import { SegmentedTabs } from '../UI/SegmentedTabs'; import { SegmentedTabs } from '../UI/SegmentedTabs';
import { useAdminStats } from '../../hooks/useAdminStats'; import { useAdminStats } from '../../hooks/useAdminStats';
const useIsMobile = () => {
const [mobile, setMobile] = useState(false);
useEffect(() => {
const mq = window.matchMedia('(max-width: 640px)');
setMobile(mq.matches);
const handler = (e) => setMobile(e.matches);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, []);
return mobile;
};
const STATUS_COLORS = { const STATUS_COLORS = {
pending_confirmation: '#94a3b8', pending_confirmation: '#94a3b8',
manual_confirmation_required: '#eab308', manual_confirmation_required: '#eab308',
@ -60,6 +72,7 @@ const CustomTooltip = ({ active, payload, label: tooltipLabel }) => {
export const AdminDashboard = () => { export const AdminDashboard = () => {
const [period, setPeriod] = useState('7d'); const [period, setPeriod] = useState('7d');
const mobile = useIsMobile();
const { stats, statusDist, dailyTrend, driverStats, economics, isLoading, error, refetch } = useAdminStats(period); const { stats, statusDist, dailyTrend, driverStats, economics, isLoading, error, refetch } = useAdminStats(period);
if (isLoading) { if (isLoading) {
@ -97,13 +110,11 @@ export const AdminDashboard = () => {
total: d.total || 0, delivered: d.delivered || 0, problems: d.problems || 0, total: d.total || 0, delivered: d.delivered || 0, problems: d.problems || 0,
})); }));
// Funnel data only outcomes
const confirmedSms1 = econ.confirmed_after_sms1 || 0; const confirmedSms1 = econ.confirmed_after_sms1 || 0;
const confirmedSms2 = econ.confirmed_after_sms2 || 0; const confirmedSms2 = econ.confirmed_after_sms2 || 0;
const confirmedManual = econ.confirmed_via_manual || 0; const confirmedManual = econ.confirmed_via_manual || 0;
const paidStorage = econ.paid_storage_count || 0; const paidStorage = econ.paid_storage_count || 0;
const cancelled = econ.cancelled_count || 0; const cancelled = econ.cancelled_count || 0;
const funnelTotal = confirmedSms1 + confirmedSms2 + confirmedManual + paidStorage + cancelled;
const funnelSteps = [ const funnelSteps = [
{ label: 'Согласовано после SMS 1', value: confirmedSms1, color: '#22c55e' }, { label: 'Согласовано после SMS 1', value: confirmedSms1, color: '#22c55e' },
@ -113,20 +124,28 @@ export const AdminDashboard = () => {
{ label: 'Отмена', value: cancelled, color: '#ef4444' }, { label: 'Отмена', value: cancelled, color: '#ef4444' },
].filter(s => s.value > 0); ].filter(s => s.value > 0);
// Responsive values
const chartHeight = mobile ? 200 : 240;
const kpiMin = mobile ? '80px' : '110px';
const chartGridCols = mobile ? '1fr' : '1fr 2fr';
const driverLabelWidth = mobile ? 80 : 120;
const fontSize = mobile ? { xs: '0.6rem', s: '0.7rem', m: '0.78rem', l: '0.85rem', xl: '1rem' }
: { xs: '0.65rem', s: '0.68rem', m: '0.78rem', l: '0.85rem', xl: '1.1rem' };
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: mobile ? '0.75rem' : '1.25rem' }}>
{/* Period selector */} {/* Period selector */}
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: '0.75rem' }}> <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem' }}>
<div> <div>
<h2 style={{ fontSize: '1.1rem', fontWeight: 600, color: 'var(--color-text)', marginBottom: '0.25rem' }}>Аналитика</h2> <h2 style={{ fontSize: mobile ? '1rem' : '1.1rem', fontWeight: 600, color: 'var(--color-text)', marginBottom: '0.15rem' }}>Аналитика</h2>
<p style={{ fontSize: '0.8rem', color: 'var(--color-text-muted)' }}>Статистика по доставкам</p> <p style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)' }}>Статистика по доставкам</p>
</div> </div>
<SegmentedTabs items={PERIOD_OPTIONS} activeKey={period} onChange={setPeriod} /> <SegmentedTabs items={PERIOD_OPTIONS} activeKey={period} onChange={setPeriod} />
</div> </div>
{/* KPI */} {/* KPI — 2 cols on mobile, auto-fit on desktop */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(110px, 1fr))', gap: '0.5rem' }}> <div style={{ display: 'grid', gridTemplateColumns: mobile ? '1fr 1fr' : `repeat(auto-fit, minmax(${kpiMin}, 1fr))`, gap: '0.4rem' }}>
{[ {[
{ label: 'Всего', val: totalGroups }, { label: 'Всего', val: totalGroups },
{ label: 'Ожидает', val: sv.pending }, { label: 'Ожидает', val: sv.pending },
@ -135,47 +154,49 @@ export const AdminDashboard = () => {
{ label: 'Проблемы', val: sv.problem }, { label: 'Проблемы', val: sv.problem },
{ label: '% доставки', val: sv.delivery_rate != null ? sv.delivery_rate + '%' : '—' }, { label: '% доставки', val: sv.delivery_rate != null ? sv.delivery_rate + '%' : '—' },
].map((kpi, i) => ( ].map((kpi, i) => (
<Panel key={i} style={{ padding: '0.5rem 0.75rem' }}> <Panel key={i} style={{ padding: mobile ? '0.4rem 0.6rem' : '0.5rem 0.75rem' }}>
<div style={{ fontSize: '0.65rem', color: 'var(--color-text-muted)', marginBottom: '0.1rem' }}>{kpi.label}</div> <div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)', marginBottom: '0.05rem' }}>{kpi.label}</div>
<div style={{ fontSize: '1.2rem', fontWeight: 700, color: 'var(--color-text)' }}>{kpi.val ?? '—'}</div> <div style={{ fontSize: mobile ? '1rem' : '1.2rem', fontWeight: 700, color: 'var(--color-text)' }}>{kpi.val ?? '—'}</div>
</Panel> </Panel>
))} ))}
</div> </div>
{/* Pie + Line */} {/* Pie + Line — stacked on mobile, side-by-side on desktop */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '1rem' }}> <div style={{ display: 'grid', gridTemplateColumns: chartGridCols, gap: mobile ? '0.5rem' : '1rem' }}>
<Panel style={{ padding: '1rem' }}> <Panel style={{ padding: mobile ? '0.75rem' : '1rem' }}>
<h3 style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.5rem', color: 'var(--color-text)' }}>По статусам</h3> <h3 style={{ fontSize: fontSize.l, fontWeight: 600, marginBottom: '0.4rem', color: 'var(--color-text)' }}>По статусам</h3>
{statusPieData.length === 0 ? ( {statusPieData.length === 0 ? (
<div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '2rem' }}>Нет данных</div> <div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1.5rem' }}>Нет данных</div>
) : ( ) : (
<ResponsiveContainer width="100%" height={240}> <ResponsiveContainer width="100%" height={chartHeight}>
<PieChart> <PieChart>
<Pie data={statusPieData} cx="50%" cy="50%" innerRadius={40} outerRadius={80} <Pie data={statusPieData} cx="50%" cy="50%"
innerRadius={mobile ? 30 : 40}
outerRadius={mobile ? 60 : 80}
dataKey="value" nameKey="name" paddingAngle={2}> dataKey="value" nameKey="name" paddingAngle={2}>
{statusPieData.map(entry => ( {statusPieData.map(entry => (
<Cell key={entry.status} fill={STATUS_COLORS[entry.status] || '#6b7280'} /> <Cell key={entry.status} fill={STATUS_COLORS[entry.status] || '#6b7280'} />
))} ))}
</Pie> </Pie>
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} />
<Legend wrapperStyle={{ fontSize: '0.65rem', color: 'var(--color-text-muted)' }} /> <Legend wrapperStyle={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)' }} />
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
)} )}
</Panel> </Panel>
<Panel style={{ padding: '1rem' }}> <Panel style={{ padding: mobile ? '0.75rem' : '1rem' }}>
<h3 style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.5rem', color: 'var(--color-text)' }}>Тренд по дням</h3> <h3 style={{ fontSize: fontSize.l, fontWeight: 600, marginBottom: '0.4rem', color: 'var(--color-text)' }}>Тренд по дням</h3>
{trendData.length === 0 ? ( {trendData.length === 0 ? (
<div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '2rem' }}>Нет данных</div> <div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1.5rem' }}>Нет данных</div>
) : ( ) : (
<ResponsiveContainer width="100%" height={240}> <ResponsiveContainer width="100%" height={chartHeight}>
<LineChart data={trendData}> <LineChart data={trendData}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border, #334155)" /> <CartesianGrid strokeDasharray="3 3" stroke="var(--color-border, #334155)" />
<XAxis dataKey="date" tick={{ fontSize: 10, fill: 'var(--color-text-muted)' }} /> <XAxis dataKey="date" tick={{ fontSize: mobile ? 9 : 10, fill: 'var(--color-text-muted)' }} />
<YAxis tick={{ fontSize: 10, fill: 'var(--color-text-muted)' }} /> <YAxis tick={{ fontSize: mobile ? 9 : 10, fill: 'var(--color-text-muted)' }} width={mobile ? 25 : 35} />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} />
<Legend wrapperStyle={{ fontSize: '0.65rem' }} /> <Legend wrapperStyle={{ fontSize: fontSize.xs }} />
<Line type="monotone" dataKey="total" name="Всего" stroke="#94a3b8" strokeWidth={2} dot={false} /> <Line type="monotone" dataKey="total" name="Всего" stroke="#94a3b8" strokeWidth={2} dot={false} />
<Line type="monotone" dataKey="delivered" name="Доставлено" stroke="#22c55e" strokeWidth={2} dot={false} /> <Line type="monotone" dataKey="delivered" name="Доставлено" stroke="#22c55e" strokeWidth={2} dot={false} />
<Line type="monotone" dataKey="problems" name="Проблемы" stroke="#ef4444" strokeWidth={2} dot={false} /> <Line type="monotone" dataKey="problems" name="Проблемы" stroke="#ef4444" strokeWidth={2} dot={false} />
@ -186,16 +207,16 @@ export const AdminDashboard = () => {
</div> </div>
{/* Status table */} {/* Status table */}
<Panel style={{ padding: '1rem' }}> <Panel style={{ padding: mobile ? '0.75rem' : '1rem' }}>
<h3 style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.5rem', color: 'var(--color-text)' }}>Все статусы</h3> <h3 style={{ fontSize: fontSize.l, fontWeight: 600, marginBottom: '0.4rem', color: 'var(--color-text)' }}>Все статусы</h3>
{statusPieData.length === 0 ? ( {statusPieData.length === 0 ? (
<div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1rem' }}>Нет данных</div> <div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1rem' }}>Нет данных</div>
) : ( ) : (
<div> <div>
<div style={{ <div style={{
display: 'grid', gridTemplateColumns: '10px 1fr 70px 55px', display: 'grid', gridTemplateColumns: mobile ? '8px 1fr 50px 40px' : '10px 1fr 70px 55px',
gap: '0 0.5rem', padding: '0.35rem 0.4rem', alignItems: 'center', gap: '0 0.4rem', padding: '0.3rem 0.3rem', alignItems: 'center',
borderBottom: '1px solid var(--color-border)', fontSize: '0.65rem', borderBottom: '1px solid var(--color-border)', fontSize: fontSize.xs,
color: 'var(--color-text-muted)', fontWeight: 600, color: 'var(--color-text-muted)', fontWeight: 600,
}}> }}>
<div /><div>Статус</div><div style={{ textAlign: 'right' }}>Кол-во</div><div style={{ textAlign: 'right' }}>Доля</div> <div /><div>Статус</div><div style={{ textAlign: 'right' }}>Кол-во</div><div style={{ textAlign: 'right' }}>Доля</div>
@ -204,14 +225,14 @@ export const AdminDashboard = () => {
const pct = totalGroups > 0 ? ((s.value / totalGroups) * 100).toFixed(1) : 0; const pct = totalGroups > 0 ? ((s.value / totalGroups) * 100).toFixed(1) : 0;
return ( return (
<div key={s.status} style={{ <div key={s.status} style={{
display: 'grid', gridTemplateColumns: '10px 1fr 70px 55px', display: 'grid', gridTemplateColumns: mobile ? '8px 1fr 50px 40px' : '10px 1fr 70px 55px',
gap: '0 0.5rem', padding: '0.45rem 0.4rem', alignItems: 'center', gap: '0 0.4rem', padding: '0.4rem 0.3rem', alignItems: 'center',
borderBottom: '1px solid var(--color-border, rgba(51,65,85,0.4))', borderBottom: '1px solid var(--color-border, rgba(51,65,85,0.4))',
}}> }}>
<div style={{ width: '10px', height: '10px', borderRadius: '3px', background: STATUS_COLORS[s.status] || '#6b7280' }} /> <div style={{ width: mobile ? '8px' : '10px', height: mobile ? '8px' : '10px', borderRadius: '3px', background: STATUS_COLORS[s.status] || '#6b7280' }} />
<div style={{ fontSize: '0.78rem', color: 'var(--color-text)' }}>{s.name}</div> <div style={{ fontSize: fontSize.m, color: 'var(--color-text)' }}>{s.name}</div>
<div style={{ textAlign: 'right', fontSize: '0.78rem', fontWeight: 600, color: 'var(--color-text)' }}>{s.value}</div> <div style={{ textAlign: 'right', fontSize: fontSize.m, fontWeight: 600, color: 'var(--color-text)' }}>{s.value}</div>
<div style={{ textAlign: 'right', fontSize: '0.75rem', color: 'var(--color-text-muted)' }}>{pct}%</div> <div style={{ textAlign: 'right', fontSize: fontSize.s, color: 'var(--color-text-muted)' }}>{pct}%</div>
</div> </div>
); );
})} })}
@ -220,39 +241,38 @@ export const AdminDashboard = () => {
</Panel> </Panel>
{/* Воронка согласования */} {/* Воронка согласования */}
<Panel style={{ padding: '1rem' }}> <Panel style={{ padding: mobile ? '0.75rem' : '1rem' }}>
<h3 style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.75rem', color: 'var(--color-text)' }}>Воронка согласования</h3> <h3 style={{ fontSize: fontSize.l, fontWeight: 600, marginBottom: '0.5rem', color: 'var(--color-text)' }}>Воронка согласования</h3>
{totalGroups === 0 ? ( {totalGroups === 0 ? (
<div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1rem' }}>Нет данных</div> <div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1rem' }}>Нет данных</div>
) : ( ) : (
<div> <div>
{/* Funnel bars — outcomes only */}
{funnelSteps.length === 0 ? ( {funnelSteps.length === 0 ? (
<div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1rem' }}>Нет завершённых согласований</div> <div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1rem' }}>Нет завершённых согласований</div>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0', padding: '0.5rem 0' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '0', padding: '0.4rem 0' }}>
{funnelSteps.map((step, i) => { {funnelSteps.map((step, i) => {
const pct = totalGroups > 0 ? Math.round((step.value / totalGroups) * 100) : 0; const pct = totalGroups > 0 ? Math.round((step.value / totalGroups) * 100) : 0;
const widthPct = Math.max(12, (step.value / totalGroups) * 100); const widthPct = Math.max(15, (step.value / totalGroups) * 100);
return ( return (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px', width: '100%' }}> <div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1px', width: '100%' }}>
<div style={{ fontSize: '0.85rem', fontWeight: 700, color: 'var(--color-text)', textAlign: 'center' }}> <div style={{ fontSize: mobile ? '0.8rem' : '0.85rem', fontWeight: 700, color: 'var(--color-text)', textAlign: 'center' }}>
{step.value} {step.value}
</div> </div>
<div style={{ <div style={{
width: widthPct + '%', height: '32px', background: step.color, width: widthPct + '%', height: mobile ? '28px' : '32px', background: step.color,
borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'width 0.4s ease', minWidth: '50px', maxWidth: '100%', transition: 'width 0.4s ease', minWidth: '40px', maxWidth: '100%',
}}> }}>
<span style={{ fontSize: '0.7rem', fontWeight: 600, color: '#fff', textShadow: '0 1px 2px rgba(0,0,0,0.3)' }}> <span style={{ fontSize: mobile ? '0.6rem' : '0.7rem', fontWeight: 600, color: '#fff', textShadow: '0 1px 2px rgba(0,0,0,0.3)' }}>
{pct}% {pct}%
</span> </span>
</div> </div>
<div style={{ fontSize: '0.72rem', color: 'var(--color-text-muted)', textAlign: 'center', maxWidth: '220px' }}> <div style={{ fontSize: mobile ? '0.65rem' : '0.72rem', color: 'var(--color-text-muted)', textAlign: 'center', maxWidth: mobile ? '180px' : '220px' }}>
{step.label} {step.label}
</div> </div>
{i < funnelSteps.length - 1 && ( {i < funnelSteps.length - 1 && (
<div style={{ width: '2px', height: '6px', background: 'var(--color-border)' }} /> <div style={{ width: '2px', height: '4px', background: 'var(--color-border)' }} />
)} )}
</div> </div>
); );
@ -260,59 +280,56 @@ export const AdminDashboard = () => {
</div> </div>
)} )}
{/* Summary */}
<div style={{ <div style={{
display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.75rem', display: 'grid', gridTemplateColumns: mobile ? '1fr 1fr' : '1fr 1fr 1fr', gap: '0.5rem',
marginTop: '1rem', paddingTop: '0.75rem', borderTop: '1px solid var(--color-border)', marginTop: '0.75rem', paddingTop: '0.6rem', borderTop: '1px solid var(--color-border)',
}}> }}>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '0.68rem', color: '#22c55e', marginBottom: '2px' }}>Автосогласование</div> <div style={{ fontSize: fontSize.xs, color: '#22c55e', marginBottom: '1px' }}>Автосогласование</div>
<div style={{ fontSize: '1.05rem', fontWeight: 700, color: '#22c55e' }}>{econ.auto_confirm_pct ?? 0}%</div> <div style={{ fontSize: mobile ? '0.95rem' : '1.05rem', fontWeight: 700, color: '#22c55e' }}>{econ.auto_confirm_pct ?? 0}%</div>
</div> </div>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '0.68rem', color: '#ef4444', marginBottom: '2px' }}>Ручное вмешательство</div> <div style={{ fontSize: fontSize.xs, color: '#ef4444', marginBottom: '1px' }}>Ручное вмешательство</div>
<div style={{ fontSize: '1.05rem', fontWeight: 700, color: '#ef4444' }}>{econ.manual_intervention_pct ?? 0}%</div> <div style={{ fontSize: mobile ? '0.95rem' : '1.05rem', fontWeight: 700, color: '#ef4444' }}>{econ.manual_intervention_pct ?? 0}%</div>
</div> </div>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center', display: mobile ? 'none' : 'block' }}>
<div style={{ fontSize: '0.68rem', color: 'var(--color-text-muted)', marginBottom: '2px' }}>Всего согласовано</div> <div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)', marginBottom: '1px' }}>Всего согласовано</div>
<div style={{ fontSize: '1.05rem', fontWeight: 700, color: 'var(--color-text)' }}>{econ.confirmed_auto_total ?? 0}</div> <div style={{ fontSize: mobile ? '0.95rem' : '1.05rem', fontWeight: 700, color: 'var(--color-text)' }}>{econ.confirmed_auto_total ?? 0}</div>
</div> </div>
</div> </div>
</div> </div>
)} )}
</Panel> </Panel>
{/* SMS — отдельно */} {/* SMS */}
<Panel style={{ padding: '1rem' }}> <Panel style={{ padding: mobile ? '0.75rem' : '1rem' }}>
<h3 style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.5rem', color: 'var(--color-text)' }}>SMS</h3> <h3 style={{ fontSize: fontSize.l, fontWeight: 600, marginBottom: '0.4rem', color: 'var(--color-text)' }}>SMS</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.75rem' }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.5rem' }}>
<div style={{ textAlign: 'center' }}> {[
<div style={{ fontSize: '0.68rem', color: 'var(--color-text-muted)', marginBottom: '2px' }}>SMS 1 отправлено</div> { label: 'SMS 1', val: econ.sms1_sent_count ?? 0 },
<div style={{ fontSize: '1.1rem', fontWeight: 700, color: 'var(--color-text)' }}>{econ.sms1_sent_count ?? 0}</div> { label: 'SMS 2', val: econ.sms2_sent_count ?? 0 },
</div> { label: 'Всего', val: (econ.sms1_sent_count || 0) + (econ.sms2_sent_count || 0) },
<div style={{ textAlign: 'center' }}> ].map((item, i) => (
<div style={{ fontSize: '0.68rem', color: 'var(--color-text-muted)', marginBottom: '2px' }}>SMS 2 отправлено</div> <div key={i} style={{ textAlign: 'center' }}>
<div style={{ fontSize: '1.1rem', fontWeight: 700, color: 'var(--color-text)' }}>{econ.sms2_sent_count ?? 0}</div> <div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)', marginBottom: '1px' }}>{item.label}</div>
</div> <div style={{ fontSize: mobile ? '1rem' : '1.1rem', fontWeight: 700, color: 'var(--color-text)' }}>{item.val}</div>
<div style={{ textAlign: 'center' }}> </div>
<div style={{ fontSize: '0.68rem', color: 'var(--color-text-muted)', marginBottom: '2px' }}>Всего SMS</div> ))}
<div style={{ fontSize: '1.1rem', fontWeight: 700, color: 'var(--color-text)' }}>{econ.sms1_sent_count + econ.sms2_sent_count ?? 0}</div>
</div>
</div> </div>
</Panel> </Panel>
{/* Drivers */} {/* Drivers */}
<Panel style={{ padding: '1rem' }}> <Panel style={{ padding: mobile ? '0.75rem' : '1rem' }}>
<h3 style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.5rem', color: 'var(--color-text)' }}>По водителям</h3> <h3 style={{ fontSize: fontSize.l, fontWeight: 600, marginBottom: '0.4rem', color: 'var(--color-text)' }}>По водителям</h3>
{driverData.length === 0 ? ( {driverData.length === 0 ? (
<div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '2rem' }}>Нет данных</div> <div style={{ color: 'var(--color-text-muted)', textAlign: 'center', padding: '1.5rem' }}>Нет данных</div>
) : ( ) : (
<ResponsiveContainer width="100%" height={Math.max(180, driverData.length * 45)}> <ResponsiveContainer width="100%" height={Math.max(150, driverData.length * (mobile ? 35 : 45))}>
<BarChart data={driverData} layout="vertical" margin={{ left: 20, right: 20 }}> <BarChart data={driverData} layout="vertical" margin={{ left: mobile ? 5 : 20, right: mobile ? 5 : 20 }}>
<XAxis type="number" tick={{ fontSize: 10, fill: 'var(--color-text-muted)' }} /> <XAxis type="number" tick={{ fontSize: mobile ? 9 : 10, fill: 'var(--color-text-muted)' }} />
<YAxis type="category" dataKey="name" tick={{ fontSize: 10, fill: 'var(--color-text-muted)' }} width={120} /> <YAxis type="category" dataKey="name" tick={{ fontSize: mobile ? 9 : 10, fill: 'var(--color-text-muted)' }} width={driverLabelWidth} />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} />
<Legend wrapperStyle={{ fontSize: '0.65rem' }} /> <Legend wrapperStyle={{ fontSize: fontSize.xs }} />
<Bar dataKey="delivered" name="Доставлено" fill="#22c55e" stackId="a" /> <Bar dataKey="delivered" name="Доставлено" fill="#22c55e" stackId="a" />
<Bar dataKey="problems" name="Проблемы" fill="#ef4444" stackId="a" /> <Bar dataKey="problems" name="Проблемы" fill="#ef4444" stackId="a" />
</BarChart> </BarChart>