Yaltopia-FIFA/components/competitions/competition-draft-panel.tsx
Kirubel-Kibru-Yaltopia 89440985f1
Some checks failed
Deploy to Cloudflare Workers / deploy (push) Has been cancelled
x
2026-05-24 21:46:10 +03:00

243 lines
8.0 KiB
TypeScript

"use client";
import { useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { api } from "@/lib/api/client";
import { GlassCard } from "@/components/ui/glass-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { TeamIconPicker } from "@/components/teams/team-icon-picker";
import { TeamIcon } from "@/components/teams/TeamIcon";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { Trash2 } from "lucide-react";
export type DraftTeam = {
id: string;
name: string;
nickname: string | null;
icon: string | null;
logo_path: string | null;
};
export function CompetitionDraftPanel({
competitionId,
initialTeams,
}: {
competitionId: string;
initialTeams: DraftTeam[];
}) {
const router = useRouter();
const [teams, setTeams] = useState<DraftTeam[]>(initialTeams);
const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
const [icon, setIcon] = useState("shield");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [confirmAction, setConfirmAction] = useState<
| { type: "activate" }
| { type: "fixtures" }
| { type: "delete-team"; team: DraftTeam }
| null
>(null);
const refreshTeams = useCallback(async () => {
const list = (await api.competitions.listTeams(competitionId)) as DraftTeam[];
setTeams(list);
}, [competitionId]);
async function run(fn: () => Promise<void>, refresh = true) {
setLoading(true);
setError(null);
try {
await fn();
if (refresh) {
await refreshTeams();
router.refresh();
}
} catch (e) {
setError(e instanceof Error ? e.message : "Error");
} finally {
setLoading(false);
setConfirmAction(null);
}
}
async function handleAddTeam(e: React.FormEvent) {
e.preventDefault();
if (loading) return;
const trimmed = name.trim();
if (!trimmed) return;
setLoading(true);
setError(null);
try {
const created = (await api.competitions.createTeam(competitionId, {
name: trimmed,
nickname: nickname.trim() || undefined,
icon,
})) as DraftTeam;
setTeams((prev) => [...prev, created]);
setName("");
setNickname("");
setIcon("shield");
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to add team");
} finally {
setLoading(false);
}
}
return (
<div className="space-y-4">
{error && <p className="text-sm text-red-400">{error}</p>}
<div className="flex flex-wrap gap-2">
<Button
variant="secondary"
disabled={loading}
onClick={() => setConfirmAction({ type: "activate" })}
>
Activate competition
</Button>
<Button
disabled={loading}
onClick={() => setConfirmAction({ type: "fixtures" })}
>
Generate fixtures
</Button>
</div>
<GlassCard title="Add team">
<form onSubmit={handleAddTeam} className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="team_name">Team name</Label>
<Input
id="team_name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="FC Example"
required
className="mt-1"
/>
</div>
<div>
<Label htmlFor="team_nickname">Nickname</Label>
<Input
id="team_nickname"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
placeholder="The Blues"
className="mt-1"
/>
</div>
</div>
<div>
<Label className="mb-2 block">Team icon</Label>
<TeamIconPicker value={icon} onChange={setIcon} />
</div>
<Button type="submit" disabled={loading}>
{loading ? "Adding…" : "Add team"}
</Button>
</form>
</GlassCard>
<GlassCard title={`Teams (${teams.length})`}>
{teams.length === 0 ? (
<p className="text-sm text-[var(--color-muted)]">
No teams yet. Add your first team above.
</p>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/10 text-left text-[var(--color-muted)]">
<th className="pb-3 pr-4">Team</th>
<th className="pb-3 pr-4">Nickname</th>
<th className="pb-3 w-16" />
</tr>
</thead>
<tbody>
{teams.map((t) => (
<tr key={t.id} className="border-b border-white/5">
<td className="py-3 pr-4">
<div className="flex items-center gap-2 font-medium">
<TeamIcon
icon={t.icon}
className="h-5 w-5 text-cyan-400"
/>
{t.name}
</div>
</td>
<td className="py-3 pr-4 text-[var(--color-muted)]">
{t.nickname || "—"}
</td>
<td className="py-3 text-right">
<Button
type="button"
variant="ghost"
size="sm"
disabled={loading}
onClick={() =>
setConfirmAction({ type: "delete-team", team: t })
}
>
<Trash2 className="h-4 w-4 text-red-400" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</GlassCard>
<ConfirmDialog
open={confirmAction?.type === "activate"}
onOpenChange={(o) => !o && setConfirmAction(null)}
title="Activate competition?"
description="This snapshots league rules and opens the season. Teams cannot be added casually after activation without master access."
confirmLabel="Activate"
loading={loading}
variant="primary"
onConfirm={() =>
run(() => api.competitions.activate(competitionId), true)
}
/>
<ConfirmDialog
open={confirmAction?.type === "fixtures"}
onOpenChange={(o) => !o && setConfirmAction(null)}
title="Generate fixtures?"
description="This creates all matches for every team. Make sure your team list is final."
confirmLabel="Generate"
loading={loading}
variant="primary"
onConfirm={() =>
run(() => api.competitions.generateFixtures(competitionId), true)
}
/>
<ConfirmDialog
open={confirmAction?.type === "delete-team"}
onOpenChange={(o) => !o && setConfirmAction(null)}
title="Remove team?"
description={`Delete "${confirmAction?.type === "delete-team" ? confirmAction.team.name : ""}" from this competition? This cannot be undone.`}
confirmLabel="Delete team"
loading={loading}
onConfirm={() => {
if (confirmAction?.type !== "delete-team") return;
const id = confirmAction.team.id;
return run(async () => {
await api.teams.delete(id);
setTeams((prev) => prev.filter((t) => t.id !== id));
}, true);
}}
/>
</div>
);
}