alabuga/frontend/src/app/admin/page.tsx
2025-09-28 20:29:36 +03:00

174 lines
6.9 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 { AdminBranchManager } from '../../components/admin/AdminBranchManager';
import { AdminMissionManager } from '../../components/admin/AdminMissionManager';
import { AdminRankManager } from '../../components/admin/AdminRankManager';
import { AdminArtifactManager } from '../../components/admin/AdminArtifactManager';
import { AdminSubmissionCard } from '../../components/admin/AdminSubmissionCard';
import { apiFetch } from '../../lib/api';
import { requireRole } from '../../lib/auth/session';
interface Submission {
id: number;
mission_id: number;
status: string;
comment: string | null;
proof_url: string | null;
resume_link: string | null;
passport_url: string | null;
photo_url: string | null;
resume_url: string | null;
awarded_xp: number;
awarded_mana: number;
updated_at: string;
}
interface MissionSummary {
id: number;
title: string;
description: string;
xp_reward: number;
mana_reward: number;
difficulty: 'easy' | 'medium' | 'hard';
is_active: boolean;
}
interface BranchSummary {
id: number;
title: string;
description: string;
category: string;
missions: Array<{ mission_id: number; mission_title: string; order: number }>;
}
interface RankSummary {
id: number;
title: string;
description: string;
required_xp: number;
}
interface CompetencySummary {
id: number;
name: string;
description: string;
category: string;
}
interface ArtifactSummary {
id: number;
name: string;
description: string;
rarity: string;
image_url?: string | null;
}
interface SubmissionStats {
pending: number;
approved: number;
rejected: number;
}
interface BranchCompletionStat {
branch_id: number;
branch_title: string;
completion_rate: number;
}
interface AdminStats {
total_users: number;
active_pilots: number;
average_completed_missions: number;
submission_stats: SubmissionStats;
branch_completion: BranchCompletionStat[];
}
export default async function AdminPage() {
// Админ-панель доступна только HR-сотрудникам; проверяем роль до загрузки данных.
const session = await requireRole('hr');
const [submissions, missions, branches, ranks, competencies, artifacts, stats] = await Promise.all([
apiFetch<Submission[]>('/api/admin/submissions', { authToken: session.token }),
apiFetch<MissionSummary[]>('/api/admin/missions', { authToken: session.token }),
apiFetch<BranchSummary[]>('/api/admin/branches', { authToken: session.token }),
apiFetch<RankSummary[]>('/api/admin/ranks', { authToken: session.token }),
apiFetch<CompetencySummary[]>('/api/admin/competencies', { authToken: session.token }),
apiFetch<ArtifactSummary[]>('/api/admin/artifacts', { authToken: session.token }),
apiFetch<AdminStats>('/api/admin/stats', { authToken: session.token })
]);
return (
<section>
<h2>HR-панель</h2>
<p style={{ color: 'var(--text-muted)' }}>
Управляйте миссиями, ветками и рангами, а также следите за очередью модерации отчётов.
</p>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: '1.5rem' }}>
<div className="card" style={{ gridColumn: '1 / -1', display: 'grid', gap: '1rem' }}>
<h3>Сводка</h3>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '1rem' }}>
<div className="card" style={{ marginBottom: 0 }}>
<span className="badge">Пилоты</span>
<p style={{ fontSize: '2rem', margin: '1rem 0 0' }}>{stats.total_users}</p>
<small style={{ color: 'var(--text-muted)' }}>Всего зарегистрировано</small>
</div>
<div className="card" style={{ marginBottom: 0 }}>
<span className="badge">Активность</span>
<p style={{ fontSize: '2rem', margin: '1rem 0 0' }}>{stats.active_pilots}</p>
<small style={{ color: 'var(--text-muted)' }}>Закрыли хотя бы одну миссию</small>
</div>
<div className="card" style={{ marginBottom: 0 }}>
<span className="badge">Средний прогресс</span>
<p style={{ fontSize: '2rem', margin: '1rem 0 0' }}>{stats.average_completed_missions.toFixed(1)}</p>
<small style={{ color: 'var(--text-muted)' }}>Миссий на пилота</small>
</div>
</div>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '1rem' }}>
<div className="card" style={{ marginBottom: 0 }}>
<strong>Очередь модерации</strong>
<p style={{ marginTop: '0.5rem' }}>На проверке: {stats.submission_stats.pending}</p>
<small style={{ color: 'var(--text-muted)' }}>
Одобрено: {stats.submission_stats.approved} · Отклонено: {stats.submission_stats.rejected}
</small>
</div>
<div className="card" style={{ marginBottom: 0 }}>
<strong>Завершённость веток</strong>
<ul style={{ listStyle: 'none', margin: '0.75rem 0 0', padding: 0 }}>
{stats.branch_completion.map((branchStat) => (
<li key={branchStat.branch_id} style={{ marginBottom: '0.25rem' }}>
{branchStat.branch_title}: {(branchStat.completion_rate * 100).toFixed(0)}%
</li>
))}
</ul>
</div>
</div>
</div>
<div className="card" style={{ gridColumn: '1 / -1' }}>
<h3>Очередь модерации</h3>
<p style={{ color: 'var(--text-muted)' }}>
Список последующих отправок миссий. Для полноты UX можно добавить действия approve/reject прямо отсюда.
</p>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: '1rem' }}>
{submissions.map((submission) => (
<AdminSubmissionCard key={submission.id} submission={submission} token={session.token} />
))}
{submissions.length === 0 && <p>Очередь пуста все миссии проверены.</p>}
</div>
</div>
<AdminBranchManager token={session.token} branches={branches} />
<AdminMissionManager
token={session.token}
missions={missions}
branches={branches}
ranks={ranks}
competencies={competencies}
artifacts={artifacts}
/>
<AdminRankManager token={session.token} ranks={ranks} missions={missions} competencies={competencies} />
<AdminArtifactManager token={session.token} artifacts={artifacts} />
</div>
</section>
);
}