diff --git a/app/api/admin/dashboard/route.js b/app/api/admin/dashboard/route.js
new file mode 100644
index 0000000..463aa35
--- /dev/null
+++ b/app/api/admin/dashboard/route.js
@@ -0,0 +1,15 @@
+import { NextResponse } from 'next/server';
+import { requireUser } from '@/lib/session';
+
+const ENGINE_URL = process.env.ENGINE_URL || 'http://127.0.0.1:3030';
+const ENGINE_SECRET = process.env.ENGINE_SECRET || '';
+
+export async function GET() {
+ const user = await requireUser();
+ if (!user?.isAdmin) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
+ const res = await fetch(`${ENGINE_URL}/api/admin/dashboard`, {
+ headers: { 'x-internal-secret': ENGINE_SECRET, 'x-user-id': String(user.id) },
+ cache: 'no-store',
+ });
+ return NextResponse.json(await res.json());
+}
diff --git a/app/system/page.js b/app/system/page.js
index eba8a8d..538e06d 100644
--- a/app/system/page.js
+++ b/app/system/page.js
@@ -13,7 +13,7 @@ export default async function SystemPage({ searchParams }) {
return (
<>
-
+
>
);
}
diff --git a/components/AdminPanel.js b/components/AdminPanel.js
index e43c2a2..a3783ba 100644
--- a/components/AdminPanel.js
+++ b/components/AdminPanel.js
@@ -8,12 +8,13 @@ import AdminBilling from './admin/AdminBilling';
// Sidebar navigation
// ──────────────────────────────────────────────────────────────
const SECTIONS = [
- { id: 'settings', label: 'AI-провайдеры', icon: Settings2, desc: 'Ключи aiprimetech и routerai' },
- { id: 'engine', label: 'Движок', icon: Zap, desc: 'URL, Telegram, авто-черновики' },
- { id: 'payments', label: 'ЮKassa', icon: CreditCard, desc: 'Ключи для приёма оплат' },
- { id: 'spending', label: 'Расходы AI', icon: TrendingUp, desc: 'aiprimetech + routerai' },
- { id: 'plans', label: 'Тарифы', icon: BarChart3, desc: 'Планы, кредиты, операции' },
- { id: 'billing', label: 'Пользователи', icon: Users, desc: 'Балансы и кредиты' },
+ { id: 'dashboard', label: 'Сводка', icon: BarChart3, desc: 'Пользователи, посты, финансы' },
+ { id: 'settings', label: 'AI-провайдеры', icon: Settings2, desc: 'Ключи aiprimetech и routerai' },
+ { id: 'engine', label: 'Движок', icon: Zap, desc: 'URL, Telegram, авто-черновики' },
+ { id: 'payments', label: 'ЮKassa', icon: CreditCard, desc: 'Ключи для приёма оплат' },
+ { id: 'spending', label: 'Расходы AI', icon: TrendingUp, desc: 'aiprimetech + routerai' },
+ { id: 'plans', label: 'Тарифы', icon: BarChart3, desc: 'Планы, кредиты, операции' },
+ { id: 'billing', label: 'Пользователи', icon: Users, desc: 'Балансы и кредиты' },
];
export default function AdminPanel({ initialSection = 'settings' }) {
@@ -56,6 +57,7 @@ export default function AdminPanel({ initialSection = 'settings' }) {
{/* Content */}
+ {section === 'dashboard' && }
{section === 'settings' && }
{section === 'engine' && }
{section === 'payments' && }
@@ -467,3 +469,144 @@ function PlansSection() {
);
}
+
+// ──────────────────────────────────────────────────────────────
+// Dashboard section
+// ──────────────────────────────────────────────────────────────
+function DashboardSection() {
+ const [data, setData] = useState(null);
+ const [loading, setLoading]= useState(true);
+
+ async function load() {
+ setLoading(true);
+ try {
+ const res = await fetch('/api/admin/dashboard').then(r => r.json());
+ setData(res);
+ } catch {}
+ setLoading(false);
+ }
+
+ if (!data && !loading) load();
+ if (!data && loading) { load(); }
+
+ const PLATFORM_ICONS = { telegram: '✈️', vk: '🔵', max: '🟣' };
+
+ function Stat({ label, value, sub, accent }) {
+ return (
+
+
{value}
+
{label}
+ {sub &&
{sub}
}
+
+ );
+ }
+
+ return (
+
+
+
Сводка
+
+
+
+ {loading &&
}
+
+ {data && (<>
+ {/* Пользователи */}
+
+
Пользователи
+
+
+
+
+
+
+
+ {/* Каналы */}
+
+
Каналы
+
+ s + c.cnt, 0)} />
+ {data.channels.map(c => (
+
+ ))}
+
+
+
+ {/* Посты */}
+
+
+ {/* Финансы */}
+
+
Финансы
+
+
+
₽{data.revenue.month_rub.toLocaleString('ru-RU')}
+
Выручка за 30 дней
+
{data.revenue.paid_count} платежей • итого ₽{data.revenue.total_rub.toLocaleString('ru-RU')}
+
+
+
₽{data.ai.cost_rub.toFixed(2)}
+
Расходы на AI за 30 дней
+
+ {data.ai.calls} запросов • {data.ai.errors} ошибок
+
+
+
+
+
+ {/* Черновики */}
+ {data.drafts.pending > 0 && (
+
+
+
⚡ {data.drafts.pending} черновиков ждут одобрения
+
Просмотрите и запланируйте публикацию
+
+
Смотреть →
+
+ )}
+
+ {/* Регистрации 14 дней */}
+ {data.registrations_14d.length > 0 && (
+
+
Регистрации — последние 14 дней
+
+
+ {(() => {
+ const max = Math.max(...data.registrations_14d.map(r => r.cnt), 1);
+ // Заполняем пустые дни
+ const days = [];
+ for (let i = 13; i >= 0; i--) {
+ const d = new Date(); d.setDate(d.getDate() - i);
+ const key = d.toISOString().split('T')[0];
+ const found = data.registrations_14d.find(r => r.day === key);
+ days.push({ day: key, cnt: found?.cnt || 0 });
+ }
+ return days.map((r, i) => (
+
+
+ {i % 7 === 6 &&
+ {new Date(r.day).toLocaleDateString('ru-RU', { day: '2-digit', month: 'short' })}
+
}
+
+ ));
+ })()}
+
+
+
+ )}
+ >)}
+
+ );
+}