diff --git a/src/components/admin/AdminDashboard.jsx b/src/components/admin/AdminDashboard.jsx
index 236f263..4d676ec 100644
--- a/src/components/admin/AdminDashboard.jsx
+++ b/src/components/admin/AdminDashboard.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import {
BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer,
PieChart, Pie, Cell, Legend, LineChart, Line, CartesianGrid,
@@ -8,6 +8,18 @@ import { Badge } from '../UI/Badge';
import { SegmentedTabs } from '../UI/SegmentedTabs';
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 = {
pending_confirmation: '#94a3b8',
manual_confirmation_required: '#eab308',
@@ -60,6 +72,7 @@ const CustomTooltip = ({ active, payload, label: tooltipLabel }) => {
export const AdminDashboard = () => {
const [period, setPeriod] = useState('7d');
+ const mobile = useIsMobile();
const { stats, statusDist, dailyTrend, driverStats, economics, isLoading, error, refetch } = useAdminStats(period);
if (isLoading) {
@@ -97,13 +110,11 @@ export const AdminDashboard = () => {
total: d.total || 0, delivered: d.delivered || 0, problems: d.problems || 0,
}));
- // Funnel data — only outcomes
const confirmedSms1 = econ.confirmed_after_sms1 || 0;
const confirmedSms2 = econ.confirmed_after_sms2 || 0;
const confirmedManual = econ.confirmed_via_manual || 0;
const paidStorage = econ.paid_storage_count || 0;
const cancelled = econ.cancelled_count || 0;
- const funnelTotal = confirmedSms1 + confirmedSms2 + confirmedManual + paidStorage + cancelled;
const funnelSteps = [
{ label: 'Согласовано после SMS 1', value: confirmedSms1, color: '#22c55e' },
@@ -113,20 +124,28 @@ export const AdminDashboard = () => {
{ label: 'Отмена', value: cancelled, color: '#ef4444' },
].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 (
-
+
{/* Period selector */}
-
+
-
Аналитика
-
Статистика по доставкам
+
Аналитика
+
Статистика по доставкам
- {/* KPI */}
-
+ {/* KPI — 2 cols on mobile, auto-fit on desktop */}
+
{[
{ label: 'Всего', val: totalGroups },
{ label: 'Ожидает', val: sv.pending },
@@ -135,47 +154,49 @@ export const AdminDashboard = () => {
{ label: 'Проблемы', val: sv.problem },
{ label: '% доставки', val: sv.delivery_rate != null ? sv.delivery_rate + '%' : '—' },
].map((kpi, i) => (
-
- {kpi.label}
- {kpi.val ?? '—'}
+
+ {kpi.label}
+ {kpi.val ?? '—'}
))}
- {/* Pie + Line */}
-
-
- По статусам
+ {/* Pie + Line — stacked on mobile, side-by-side on desktop */}
+
+
+ По статусам
{statusPieData.length === 0 ? (
- Нет данных
+ Нет данных
) : (
-
+
-
{statusPieData.map(entry => (
|
))}
} />
-
+
)}
-
- Тренд по дням
+
+ Тренд по дням
{trendData.length === 0 ? (
- Нет данных
+ Нет данных
) : (
-
+
-
-
+
+
} />
-
+
@@ -186,16 +207,16 @@ export const AdminDashboard = () => {
{/* Status table */}
-
- Все статусы
+
+ Все статусы
{statusPieData.length === 0 ? (
Нет данных
) : (
Статус
Кол-во
Доля
@@ -204,14 +225,14 @@ export const AdminDashboard = () => {
const pct = totalGroups > 0 ? ((s.value / totalGroups) * 100).toFixed(1) : 0;
return (
-
-
{s.name}
-
{s.value}
-
{pct}%
+
+
{s.name}
+
{s.value}
+
{pct}%
);
})}
@@ -220,39 +241,38 @@ export const AdminDashboard = () => {
{/* Воронка согласования */}
-
- Воронка согласования
+
+ Воронка согласования
{totalGroups === 0 ? (
Нет данных
) : (
- {/* Funnel bars — outcomes only */}
{funnelSteps.length === 0 ? (
Нет завершённых согласований
) : (
-
+
{funnelSteps.map((step, i) => {
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 (
-
-
+
+
{step.value}
-
+
{pct}%
-
+
{step.label}
{i < funnelSteps.length - 1 && (
-
+
)}
);
@@ -260,59 +280,56 @@ export const AdminDashboard = () => {
)}
- {/* Summary */}
-
Автосогласование
-
{econ.auto_confirm_pct ?? 0}%
+
Автосогласование
+
{econ.auto_confirm_pct ?? 0}%
-
Ручное вмешательство
-
{econ.manual_intervention_pct ?? 0}%
+
Ручное вмешательство
+
{econ.manual_intervention_pct ?? 0}%
-
-
Всего согласовано
-
{econ.confirmed_auto_total ?? 0}
+
+
Всего согласовано
+
{econ.confirmed_auto_total ?? 0}
)}
- {/* SMS — отдельно */}
-
- SMS
-
-
-
SMS 1 отправлено
-
{econ.sms1_sent_count ?? 0}
-
-
-
SMS 2 отправлено
-
{econ.sms2_sent_count ?? 0}
-
-
-
Всего SMS
-
{econ.sms1_sent_count + econ.sms2_sent_count ?? 0}
-
+ {/* SMS */}
+
+ SMS
+
+ {[
+ { label: 'SMS 1', val: econ.sms1_sent_count ?? 0 },
+ { label: 'SMS 2', val: econ.sms2_sent_count ?? 0 },
+ { label: 'Всего', val: (econ.sms1_sent_count || 0) + (econ.sms2_sent_count || 0) },
+ ].map((item, i) => (
+
+
{item.label}
+
{item.val}
+
+ ))}
{/* Drivers */}
-
- По водителям
+
+ По водителям
{driverData.length === 0 ? (
- Нет данных
+ Нет данных
) : (
-
-
-
-
+
+
+
+
} />
-
+