volsu-contests/frontend/src/app/admin/contests/[id]/edit/page.tsx
2025-11-30 19:55:50 +03:00

195 lines
6.0 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.

"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { api } from "@/lib/api";
import { useAuth } from "@/lib/auth-context";
import type { Contest } from "@/types";
export default function EditContestPage() {
const { user } = useAuth();
const router = useRouter();
const params = useParams();
const contestId = Number(params.id);
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState("");
const [formData, setFormData] = useState({
title: "",
description: "",
start_time: "",
end_time: "",
is_active: false,
});
useEffect(() => {
if (!user || user.role !== "admin") {
router.push("/contests");
return;
}
api
.getContest(contestId)
.then((contest: Contest) => {
setFormData({
title: contest.title,
description: contest.description || "",
start_time: formatDateTimeLocal(contest.start_time),
end_time: formatDateTimeLocal(contest.end_time),
is_active: contest.is_active,
});
})
.catch((err) => setError(err.message))
.finally(() => setIsLoading(false));
}, [contestId, user, router]);
const formatDateTimeLocal = (isoString: string) => {
const date = new Date(isoString);
return date.toISOString().slice(0, 16);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setIsSaving(true);
try {
await api.updateContest(contestId, {
title: formData.title,
description: formData.description || undefined,
start_time: new Date(formData.start_time).toISOString(),
end_time: new Date(formData.end_time).toISOString(),
is_active: formData.is_active,
});
router.push("/admin/contests");
} catch (err) {
setError(err instanceof Error ? err.message : "Ошибка сохранения");
} finally {
setIsSaving(false);
}
};
if (!user || user.role !== "admin") {
return null;
}
if (isLoading) {
return (
<div className="container mx-auto px-4 py-8 max-w-2xl">
<div className="animate-pulse space-y-6">
<div className="h-8 bg-muted rounded w-1/3" />
<div className="h-12 bg-muted rounded" />
<div className="h-24 bg-muted rounded" />
<div className="h-12 bg-muted rounded" />
</div>
</div>
);
}
return (
<div className="container mx-auto px-4 py-8 max-w-2xl">
<h1 className="text-3xl font-bold mb-8">Редактировать контест</h1>
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="p-4 bg-destructive/10 text-destructive rounded-lg">
{error}
</div>
)}
<div>
<label className="block text-sm font-medium mb-2">Название</label>
<input
type="text"
value={formData.title}
onChange={(e) =>
setFormData({ ...formData, title: e.target.value })
}
required
className="w-full px-4 py-2 border border-input rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">
Описание (опционально)
</label>
<textarea
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
rows={4}
className="w-full px-4 py-2 border border-input rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-2">
Время начала
</label>
<input
type="datetime-local"
value={formData.start_time}
onChange={(e) =>
setFormData({ ...formData, start_time: e.target.value })
}
required
className="w-full px-4 py-2 border border-input rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">
Время окончания
</label>
<input
type="datetime-local"
value={formData.end_time}
onChange={(e) =>
setFormData({ ...formData, end_time: e.target.value })
}
required
className="w-full px-4 py-2 border border-input rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="is_active"
checked={formData.is_active}
onChange={(e) =>
setFormData({ ...formData, is_active: e.target.checked })
}
className="w-4 h-4"
/>
<label htmlFor="is_active" className="text-sm">
Контест активен
</label>
</div>
<div className="flex gap-4">
<button
type="submit"
disabled={isSaving}
className="px-6 py-2 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 transition disabled:opacity-50"
>
{isSaving ? "Сохранение..." : "Сохранить"}
</button>
<button
type="button"
onClick={() => router.back()}
className="px-6 py-2 border border-border rounded-lg font-medium hover:bg-muted transition"
>
Отмена
</button>
</div>
</form>
</div>
);
}