Some checks failed
Deploy to Cloudflare Workers / deploy (push) Has been cancelled
201 lines
6.1 KiB
TypeScript
201 lines
6.1 KiB
TypeScript
import { redirect } from "next/navigation";
|
||
import { createClient } from "@/lib/supabase/server";
|
||
import {
|
||
FormDonut,
|
||
GoalsTrendChart,
|
||
TopScorersChart,
|
||
StatCards,
|
||
} from "@/components/manager/TeamCharts";
|
||
import { GlassCard } from "@/components/ui/glass-card";
|
||
import { TeamBadge } from "@/components/teams/TeamBadge";
|
||
|
||
export default async function MyTeamPage({
|
||
params,
|
||
}: {
|
||
params: Promise<{ leagueId: string; competitionId: string }>;
|
||
}) {
|
||
const { leagueId, competitionId } = await params;
|
||
const supabase = await createClient();
|
||
const {
|
||
data: { user },
|
||
} = await supabase.auth.getUser();
|
||
if (!user) redirect("/login");
|
||
|
||
const { data: teamsInComp } = await supabase
|
||
.from("teams")
|
||
.select("id")
|
||
.eq("competition_id", competitionId);
|
||
const teamIds = teamsInComp?.map((t) => t.id) ?? [];
|
||
|
||
const { data: membership } = await supabase
|
||
.from("team_members")
|
||
.select("team_id, teams(*)")
|
||
.eq("user_id", user.id)
|
||
.eq("role", "manager")
|
||
.in("team_id", teamIds.length ? teamIds : ["00000000-0000-0000-0000-000000000000"])
|
||
.limit(1)
|
||
.maybeSingle();
|
||
|
||
if (!membership) {
|
||
return (
|
||
<p className="text-[var(--color-muted)]">
|
||
You are not a team manager in this competition.
|
||
</p>
|
||
);
|
||
}
|
||
|
||
const team = membership.teams as {
|
||
id: string;
|
||
name: string;
|
||
logo_path: string | null;
|
||
};
|
||
|
||
const { data: results } = await supabase
|
||
.from("team_match_results")
|
||
.select("*")
|
||
.eq("team_id", team.id)
|
||
.order("matchday", { ascending: true });
|
||
|
||
const { data: playerStats } = await supabase
|
||
.from("player_competition_stats")
|
||
.select("*")
|
||
.eq("team_id", team.id)
|
||
.eq("competition_id", competitionId)
|
||
.order("goals", { ascending: false });
|
||
|
||
const formCounts = { W: 0, D: 0, L: 0 };
|
||
results?.slice(-10).forEach((r) => {
|
||
if (r.result in formCounts) formCounts[r.result as keyof typeof formCounts]++;
|
||
});
|
||
const formData = Object.entries(formCounts).map(([name, value]) => ({
|
||
name,
|
||
value,
|
||
}));
|
||
|
||
const goalsTrend =
|
||
results?.map((r, i) => ({
|
||
matchday: r.matchday ?? i + 1,
|
||
gf: r.goals_for ?? 0,
|
||
ga: r.goals_against ?? 0,
|
||
})) ?? [];
|
||
|
||
const topScorers =
|
||
playerStats?.slice(0, 8).map((p) => ({
|
||
name: p.player_name,
|
||
goals: p.goals,
|
||
})) ?? [];
|
||
|
||
const topAssists =
|
||
playerStats
|
||
?.slice()
|
||
.sort((a, b) => b.assists - a.assists)
|
||
.slice(0, 8)
|
||
.map((p) => ({
|
||
name: p.player_name,
|
||
assists: p.assists,
|
||
})) ?? [];
|
||
|
||
const played = results?.length ?? 0;
|
||
const won = results?.filter((r) => r.result === "W").length ?? 0;
|
||
const drawn = results?.filter((r) => r.result === "D").length ?? 0;
|
||
const lost = results?.filter((r) => r.result === "L").length ?? 0;
|
||
const gf = results?.reduce((s, r) => s + (r.goals_for ?? 0), 0) ?? 0;
|
||
const ga = results?.reduce((s, r) => s + (r.goals_against ?? 0), 0) ?? 0;
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<TeamBadge name={team.name} logoPath={team.logo_path} size="lg" />
|
||
<a
|
||
href={`/leagues/${leagueId}/competitions/${competitionId}/teams/${team.id}/settings`}
|
||
className="text-sm text-cyan-400 hover:underline"
|
||
>
|
||
Team settings →
|
||
</a>
|
||
</div>
|
||
|
||
<StatCards
|
||
stats={[
|
||
{ label: "Played", value: played },
|
||
{ label: "Won", value: won },
|
||
{ label: "GF", value: gf },
|
||
{ label: "GD", value: gf - ga },
|
||
]}
|
||
/>
|
||
|
||
<div className="grid gap-4 lg:grid-cols-2">
|
||
<FormDonut data={formData.filter((d) => d.value > 0)} />
|
||
<GoalsTrendChart data={goalsTrend} />
|
||
<TopScorersChart
|
||
data={topScorers}
|
||
dataKey="goals"
|
||
title="Top scorers"
|
||
/>
|
||
<TopScorersChart
|
||
data={topAssists}
|
||
dataKey="assists"
|
||
title="Top assists"
|
||
/>
|
||
</div>
|
||
|
||
<GlassCard title="Squad stats">
|
||
<table className="w-full text-sm">
|
||
<thead>
|
||
<tr className="border-b border-white/10 text-left text-[var(--color-muted)]">
|
||
<th className="pb-2">Player</th>
|
||
<th className="pb-2 text-center">Apps</th>
|
||
<th className="pb-2 text-center">G</th>
|
||
<th className="pb-2 text-center">A</th>
|
||
<th className="pb-2 text-center">G+A</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{playerStats?.map((p) => (
|
||
<tr key={p.player_id} className="border-b border-white/5">
|
||
<td className="py-2">{p.player_name}</td>
|
||
<td className="py-2 text-center">{p.appearances}</td>
|
||
<td className="py-2 text-center">{p.goals}</td>
|
||
<td className="py-2 text-center">{p.assists}</td>
|
||
<td className="py-2 text-center text-cyan-400">
|
||
{p.goals + p.assists}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</GlassCard>
|
||
|
||
<GlassCard title="Recent results">
|
||
<ul className="space-y-2">
|
||
{results
|
||
?.slice()
|
||
.reverse()
|
||
.slice(0, 5)
|
||
.map((r) => (
|
||
<li
|
||
key={r.match_id}
|
||
className="flex items-center justify-between rounded-lg bg-white/5 px-3 py-2 text-sm"
|
||
>
|
||
<span>vs {r.opponent_name}</span>
|
||
<span>
|
||
{r.goals_for}–{r.goals_against}{" "}
|
||
<span
|
||
className={
|
||
r.result === "W"
|
||
? "text-emerald-400"
|
||
: r.result === "L"
|
||
? "text-red-400"
|
||
: "text-amber-400"
|
||
}
|
||
>
|
||
{r.result}
|
||
</span>
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</GlassCard>
|
||
</div>
|
||
);
|
||
}
|