"use client"; import { Box, Flex, Text, Heading, Badge, VStack, HStack, Image, Button, Card, SimpleGrid, Skeleton, } from "@chakra-ui/react"; import { useTranslations } from "next-intl"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; import { useColorModeValue } from "@/components/ui/color-mode"; import { SlideUp, FadeIn } from "@/components/motion"; import { useMatchDetails } from "@/lib/api/matches/use-hooks"; import { usePrediction } from "@/lib/api/predictions/use-hooks"; import { useGetMe } from "@/lib/api/users/use-hooks"; import { useQueryClient } from "@tanstack/react-query"; import { UsersQueryKeys } from "@/lib/api/users/use-hooks"; import PredictionCard from "@/components/matches/prediction-card"; import OddsCard from "@/components/matches/odds-card"; import LineupsCard from "@/components/matches/lineups-card"; import { LuArrowLeft, LuRefreshCw, LuUser, LuSparkles, LuInfo, LuChevronDown, LuChevronUp, LuCalendar, LuArrowRight, } from "react-icons/lu"; import type { MatchResponseDto, MatchEvent, } from "@/lib/api/matches/types"; // ───────────────────────────────────────────────── // Types // ───────────────────────────────────────────────── interface SidelinedPlayer { playerName: string; positionShort?: string; description?: string; type?: string; matchesMissed?: number; } interface SidelinedTeam { players?: SidelinedPlayer[]; } interface SidelinedData { homeTeam?: SidelinedTeam; awayTeam?: SidelinedTeam; } // ───────────────────────────────────────────────── // Skeleton Loading // ───────────────────────────────────────────────── function MatchDetailSkeleton() { const cardBg = useColorModeValue("white", "gray.800"); const borderColor = useColorModeValue("gray.100", "gray.700"); return ( {/* Header skeleton */} {/* Tab bar skeleton */} {/* Content skeletons */} {[1, 2].map((i) => ( {[1, 2, 3, 4].map((j) => ( ))} ))} ); } // ───────────────────────────────────────────────── // Main Component // ───────────────────────────────────────────────── export default function MatchDetailContent() { const t = useTranslations("matches"); const tPred = useTranslations("predictions"); const queryClient = useQueryClient(); const { data: meData } = useGetMe(); const usageLimit = meData?.data?.usageLimit; const hasLimit = usageLimit ? usageLimit.maxAnalyses - usageLimit.analysisCount > 0 : true; const tCommon = useTranslations("common"); const params = useParams(); const router = useRouter(); const matchId = params.id as string; const { data: matchData, isLoading: matchLoading } = useMatchDetails(matchId); const { data: predictionData, isLoading: predLoading, refetch: refetchPredictionRaw, isFetching: isPredFetching, } = usePrediction(matchId); const [officialsOpen, setOfficialsOpen] = useState(false); const [activeTab, setActiveTab] = useState("all"); const refetchPrediction = async () => { await refetchPredictionRaw(); queryClient.invalidateQueries({ queryKey: UsersQueryKeys.me() }); }; // Colors const headerBg = useColorModeValue("white", "gray.800"); const cardBg = useColorModeValue("white", "gray.800"); const borderColor = useColorModeValue("gray.100", "gray.700"); const subtleBg = useColorModeValue("gray.50", "gray.900"); const injuryBg = useColorModeValue("red.50", "rgba(254,178,178,0.08)"); const injuryBorder = useColorModeValue("red.100", "red.800"); const tabBg = useColorModeValue("white", "gray.800"); const disclaimerBg = useColorModeValue("blue.50", "rgba(99,179,237,0.08)"); const disclaimerBorder = useColorModeValue("blue.100", "blue.800"); const timelineBg = useColorModeValue("gray.200", "gray.600"); const match = matchData?.data as MatchResponseDto | undefined; const prediction = predictionData?.data; if (matchLoading) return ; if (!match) { return ( {t("no-matches")} ); } // ── Derived state ────────────────────────────── const isLive = match.status === "LIVE"; const isFinished = match.status === "Finished" || match.status === "FT" || match.state === "postGame"; const winner = match.winner; // "home" | "away" | "draw" | undefined const homeWon = winner === "home"; const awayWon = winner === "away"; const matchEvents = (match.events || match.playerEvents || []) .slice() .sort((a, b) => (parseInt(a.timeMinute) || 0) - (parseInt(b.timeMinute) || 0)); const homeStats = match.stats?.home; const awayStats = match.stats?.away; const hasStats = homeStats != null && awayStats != null && (homeStats.possessionPercentage != null || homeStats.totalShots != null); const officials = match.officials || []; const mainReferee = officials.find((o) => o.roleId === 1); const otherOfficials = officials.filter((o) => o.roleId !== 1); const sidelined = (match.sidelined || {}) as SidelinedData; const hasSidelined = !isFinished && ((sidelined.homeTeam?.players?.length || 0) > 0 || (sidelined.awayTeam?.players?.length || 0) > 0); const show = (key: string) => activeTab === "all" || activeTab === key; const tabs = [ { key: "all", label: t("all-matches", { defaultValue: "Hepsi" }) }, ...(matchEvents.length > 0 ? [{ key: "events", label: t("match-events") }] : []), ...(hasStats ? [{ key: "stats", label: t("statistics") }] : []), { key: "lineups", label: t("lineups") }, { key: "prediction", label: tPred("title") }, ...((match.odds && Object.keys(match.odds).length > 0) ? [{ key: "odds", label: t("odds") }] : []), ]; return ( {/* Back Button */} {/* ══════════════════════════════════════════════ */} {/* 1. MATCH HEADER */} {/* ══════════════════════════════════════════════ */} {/* League banner */} {match.league && ( {match.league.country?.flag && ( {match.league.country.name )} {match.league.country?.name && `${match.league.country.name} · `} {match.league.name} {isLive && ( )} {isLive ? t("live") : isFinished ? t("finished") : t("not-started")} )} {/* Teams & Score */} {/* Home Team */} {match.homeTeam?.logo ? ( {match.homeTeam.name} ) : ( {match.homeTeam?.name?.charAt(0) || "H"} )} {match.homeTeam?.name} {t("home-team")} {/* Score */} {match.score && (isLive || isFinished) ? ( <> {/* FT badge */} {isFinished && ( FT )} {match.score.home} : {match.score.away} {match.score.htHome != null && match.score.htAway != null && ( ({t("half-time")}: {match.score.htHome}–{match.score.htAway}) )} ) : ( {t("vs")} )} {/* Date */} {new Date(match.mstUtc).toLocaleDateString("tr-TR", { day: "2-digit", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", })} {/* Away Team */} {match.awayTeam?.logo ? ( {match.awayTeam.name} ) : ( {match.awayTeam?.name?.charAt(0) || "A"} )} {match.awayTeam?.name} {t("away-team")} {/* Referee + Officials (collapsible) */} {(mainReferee || officials.length > 0) && ( 0 ? "pointer" : "default"} onClick={() => otherOfficials.length > 0 && setOfficialsOpen((o) => !o)} _hover={otherOfficials.length > 0 ? { opacity: 0.75 } : {}} > {t("main-referee")}:{" "} {mainReferee?.name ?? t("referee")} {otherOfficials.length > 0 && ( {officialsOpen ? : } )} {/* Other officials expanded */} {officialsOpen && otherOfficials.length > 0 && ( {otherOfficials.map((official) => ( {official.name} {officialRoleLabel(official.roleId, t)} ))} )} )} {/* ══════════════════════════════════════════════ */} {/* 2. TAB FILTER BAR */} {/* ══════════════════════════════════════════════ */} {tabs.map((tab) => { const isActive = activeTab === tab.key; return ( ); })} {/* ══════════════════════════════════════════════ */} {/* 3. INJURIES (only pre-match) */} {/* ══════════════════════════════════════════════ */} {hasSidelined && show("all") && ( 🏥 {t("sidelined")} )} {/* ══════════════════════════════════════════════ */} {/* 4. MATCH EVENTS TIMELINE */} {/* ══════════════════════════════════════════════ */} {matchEvents.length > 0 && show("events") && ( {t("match-events")} {/* Team name headers */} {match.homeTeam?.name} {match.awayTeam?.name} {/* Events */} {/* Center vertical line */} {matchEvents.map((event) => ( ))} )} {/* ══════════════════════════════════════════════ */} {/* 5. TEAM STATISTICS */} {/* ══════════════════════════════════════════════ */} {hasStats && show("stats") && ( {/* Header with team names */} {match.homeTeam?.name} {t("statistics")} {match.awayTeam?.name} {homeStats!.possessionPercentage != null && ( )} {homeStats!.totalShots != null && ( )} {homeStats!.shotsOnTarget != null && ( )} {homeStats!.shotsOffTarget != null && ( )} {homeStats!.totalPasses != null && ( )} {homeStats!.corners != null && ( )} {homeStats!.fouls != null && ( )} {homeStats!.offsides != null && ( )} )} {/* ══════════════════════════════════════════════ */} {/* 6. LINEUPS */} {/* ══════════════════════════════════════════════ */} {show("lineups") && ( )} {/* ══════════════════════════════════════════════ */} {/* 7. PREDICTION */} {/* ══════════════════════════════════════════════ */} {show("prediction") && {tPred("title")} {/* Pre-match disclaimer */} {tPred("pre-match-disclaimer")} {predLoading || isPredFetching ? ( ) : prediction ? ( ) : ( {tPred("no-predictions")} {!hasLimit && ( {tCommon("limits.out_of_analysis")} )} )} } {/* ══════════════════════════════════════════════ */} {/* 8. ODDS */} {/* ══════════════════════════════════════════════ */} {match.odds && Object.keys(match.odds).length > 0 && show("odds") && ( )} ); } // ───────────────────────────────────────────────── // Timeline Event Row // ───────────────────────────────────────────────── interface TimelineEventRowProps { event: MatchEvent; homeTeamId?: string; t: ReturnType; borderColor: string; subtleBg: string; timelineBg: string; } function TimelineEventRow({ event, homeTeamId, t, subtleBg, }: TimelineEventRowProps) { const isHome = event.teamId === homeTeamId || event.position === "home"; const isGoal = event.eventType === "goal"; const isCard = event.eventType === "card"; const isSub = event.eventType === "substitute"; const icon = (() => { if (isGoal) return "⚽"; if (isCard && event.eventSubtype === "yc") return "🟨"; if (isCard && (event.eventSubtype === "rc" || event.eventSubtype === "y2c")) return "🟥"; if (isSub) return "🔄"; return "•"; })(); const mainText = (() => { if (isGoal) { const penalty = event.eventSubtype === "penalty-goal" ? ` (${t("penalty")})` : ""; return `${event.player?.name || ""}${penalty}`; } if (isSub) return event.player?.name || ""; return event.player?.name || ""; })(); const subText = (() => { if (isGoal && event.assistPlayer) return `${t("assist")}: ${event.assistPlayer.name}`; if (isSub && event.substitutedOut) return `↓ ${event.substitutedOut.name}`; if (isCard) return event.eventSubtype === "yc" ? t("yellow-card") : t("red-card"); return null; })(); return ( {/* Home side */} {isHome && ( {mainText} {subText && ( {subText} )} {icon} {isGoal && event.scoreAfter && ( {event.scoreAfter} )} )} {/* Center minute bubble */} {event.timeMinute}' {/* Away side */} {!isHome && ( {icon} {mainText} {subText && ( {subText} )} {isGoal && event.scoreAfter && ( {event.scoreAfter} )} )} ); } // ───────────────────────────────────────────────── // Stat Bar // ───────────────────────────────────────────────── interface StatBarProps { label: string; homeVal: number; awayVal: number; suffix?: string; } function StatBar({ label, homeVal, awayVal, suffix = "" }: StatBarProps) { const total = homeVal + awayVal || 1; const homePct = (homeVal / total) * 100; const awayPct = (awayVal / total) * 100; const homeBarBg = useColorModeValue("blue.400", "blue.500"); const awayBarBg = useColorModeValue("orange.400", "orange.500"); const trackBg = useColorModeValue("gray.100", "gray.700"); const homeWins = homeVal > awayVal; const awayWins = awayVal > homeVal; return ( {homeVal} {suffix} {homeWins && } {label} {awayWins && ( )} {awayVal} {suffix} ); } // ───────────────────────────────────────────────── // Sidelined Column // ───────────────────────────────────────────────── interface SidelinedColumnProps { team?: SidelinedTeam; teamName: string; teamLogo?: string; injuryBg: string; injuryBorder: string; t: ReturnType; } function SidelinedColumn({ team, teamName, teamLogo, injuryBg, injuryBorder, t, }: SidelinedColumnProps) { const players = team?.players || []; if (players.length === 0) { return ( {teamLogo && ( {teamName} )} {teamName} {t("no-sidelined")} ); } return ( {teamLogo && ( {teamName} )} {teamName} {players.length} {players.map((player, idx) => ( {player.playerName} {player.positionShort && ( {player.positionShort} )} {player.description || (player.type === "injury" ? t("injury") : player.type === "suspended" ? t("suspended") : t("other-reason"))} {player.matchesMissed !== undefined && player.matchesMissed > 0 && ( {player.matchesMissed} {t("matches-missed")} )} ))} ); } // ───────────────────────────────────────────────── // Official Role Label // ───────────────────────────────────────────────── function officialRoleLabel( roleId: number, t: ReturnType, ): string { switch (roleId) { case 1: return t("main-referee"); case 2: return t("assistant-referee"); case 3: return t("fourth-official"); case 5: return t("var-referee"); case 6: return t("avar-referee"); default: return t("referee"); } }