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' })} +
} +
+ )); + })()} +
+
+
+ )} + )} +
+ ); +}