Files
ReSekai/src/pages/AdminPage.tsx
T
2026-02-11 01:07:43 +01:00

170 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
import { getAdminStats } from "../services/api";
import "./AdminPage.css";
interface StoryStats {
id: string;
title: string;
messageCount: number;
tokens: number;
lastPlayed: string | null;
}
interface AdminStats {
totalStories: number;
totalSessions: number;
totalTokens: number;
stories: StoryStats[];
}
export default function AdminPage() {
const navigate = useNavigate();
const { isAuthenticated, isLoading: authLoading } = useAuth();
const [stats, setStats] = useState<AdminStats | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!authLoading && !isAuthenticated) {
navigate("/");
return;
}
const loadStats = async () => {
if (!isAuthenticated) return;
setIsLoading(true);
try {
const data = await getAdminStats();
if (data) {
setStats(data);
} else {
setError("Не удалось загрузить статистику");
}
} catch (err) {
setError("Ошибка загрузки данных");
} finally {
setIsLoading(false);
}
};
loadStats();
}, [isAuthenticated, authLoading, navigate]);
const formatTokens = (tokens: number) => {
if (tokens >= 1000000) return `${(tokens / 1000000).toFixed(2)}M`;
if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K`;
return tokens.toString();
};
const formatDate = (dateStr: string | null) => {
if (!dateStr) return "Никогда";
return new Date(dateStr).toLocaleDateString("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
if (authLoading || isLoading) {
return (
<div className="admin-page">
<div className="admin-loading">Загрузка статистики...</div>
</div>
);
}
if (error) {
return (
<div className="admin-page">
<div className="admin-error">{error}</div>
</div>
);
}
if (!stats) {
return (
<div className="admin-page">
<div className="admin-error">Нет данных</div>
</div>
);
}
return (
<div className="admin-page">
<h1>📊 Статистика</h1>
<div className="stats-overview">
<div className="stat-card">
<span className="stat-icon">📚</span>
<div className="stat-info">
<span className="stat-value">{stats.totalStories}</span>
<span className="stat-label">Историй</span>
</div>
</div>
<div className="stat-card">
<span className="stat-icon">🎮</span>
<div className="stat-info">
<span className="stat-value">{stats.totalSessions}</span>
<span className="stat-label">Сессий</span>
</div>
</div>
<div className="stat-card highlight">
<span className="stat-icon">🔤</span>
<div className="stat-info">
<span className="stat-value">
{formatTokens(stats.totalTokens)}
</span>
<span className="stat-label">Всего токенов</span>
</div>
</div>
</div>
<h2>Статистика по историям</h2>
<div className="stories-table-wrapper">
<table className="stories-table">
<thead>
<tr>
<th>История</th>
<th>Сообщений</th>
<th>Токенов</th>
<th>Последняя игра</th>
</tr>
</thead>
<tbody>
{stats.stories.length === 0 ? (
<tr>
<td colSpan={4} className="no-data">
Нет историй
</td>
</tr>
) : (
stats.stories.map((story) => (
<tr key={story.id}>
<td className="story-title">{story.title}</td>
<td>{story.messageCount}</td>
<td className="tokens-cell">{formatTokens(story.tokens)}</td>
<td className="date-cell">{formatDate(story.lastPlayed)}</td>
</tr>
))
)}
</tbody>
</table>
</div>
<div className="token-info">
<p>
💡 <strong>Примечание:</strong> Токены рассчитаны приблизительно (1
токен 3 символа для русского текста). Реальное потребление может
отличаться.
</p>
</div>
</div>
);
}