3855bbd4be
- HomeworkSubmission: add status (PENDING/REVIEWING/APPROVED/REJECTED) + statusAt - HomeworkFeedback: add files (Json) + audioUrl fields - Curator detail page: meta table, content tabs, feedback history with audio/files - FeedbackForm: file upload, audio recorder (Web Audio API + S3), action buttons - AudioRecorder component: record → preview → upload to S3 - ContentTabs: toggle between homework description and lesson content (TipTap read-only) - Homework list: 4-color status badges with proper filtering - API routes: /api/curator/upload and /api/curator/audio-upload Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
import { headers } from "next/headers";
|
||
import { auth } from "@/lib/auth";
|
||
import { redirect } from "next/navigation";
|
||
import { prisma } from "@/lib/prisma";
|
||
import Link from "next/link";
|
||
|
||
export default async function CuratorDashboard() {
|
||
const session = await auth.api.getSession({ headers: await headers() });
|
||
if (!session) redirect("/login");
|
||
|
||
const [pending, total, recentFeedbacks] = await Promise.all([
|
||
prisma.homeworkSubmission.count({ where: { feedbacks: { none: {} } } }),
|
||
prisma.homeworkSubmission.count(),
|
||
prisma.homeworkFeedback.count({
|
||
where: {
|
||
createdAt: { gte: new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000) },
|
||
curatorId: session.user.id,
|
||
},
|
||
}),
|
||
]);
|
||
|
||
return (
|
||
<div className="p-8 max-w-3xl">
|
||
<h1 className="text-2xl font-bold mb-1">Обзор</h1>
|
||
<p className="text-sm mb-8" style={{ color: "var(--muted-foreground)" }}>Панель куратора</p>
|
||
|
||
<div className="grid grid-cols-3 gap-4 mb-8">
|
||
<StatCard label="Ожидают проверки" value={pending} accent={pending > 0} />
|
||
<StatCard label="Всего сдано" value={total} />
|
||
<StatCard label="Проверено за 7 дней" value={recentFeedbacks} />
|
||
</div>
|
||
|
||
{pending > 0 ? (
|
||
<Link href="/curator/homework" className="btn-aubade btn-aubade-accent inline-flex items-center gap-2 px-5 py-2.5 text-sm">
|
||
Перейти к проверке ({pending}) →
|
||
</Link>
|
||
) : (
|
||
<div className="card-aubade p-8 text-center">
|
||
<p className="text-3xl mb-2">✓</p>
|
||
<p className="font-bold">Все работы проверены</p>
|
||
<p className="text-sm mt-1" style={{ color: "var(--muted-foreground)" }}>Новых заданий нет</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function StatCard({ label, value, accent }: { label: string; value: number; accent?: boolean }) {
|
||
return (
|
||
<div className="card-aubade p-4">
|
||
<p className="text-3xl font-bold" style={{ color: accent ? "oklch(0.577 0.245 27.325)" : "var(--foreground)" }}>
|
||
{value}
|
||
</p>
|
||
<p className="text-xs mt-1 uppercase tracking-widest" style={{ color: "var(--muted-foreground)" }}>{label}</p>
|
||
</div>
|
||
);
|
||
}
|