195 lines
6.0 KiB
TypeScript
195 lines
6.0 KiB
TypeScript
"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>
|
||
);
|
||
}
|