286 lines
9.7 KiB
TypeScript
286 lines
9.7 KiB
TypeScript
"use client";
|
||
|
||
import {
|
||
Box,
|
||
Card,
|
||
Flex,
|
||
HStack,
|
||
Text,
|
||
VStack,
|
||
Badge,
|
||
SimpleGrid,
|
||
Icon,
|
||
} from "@chakra-ui/react";
|
||
import { useColorModeValue } from "@/components/ui/color-mode";
|
||
import { LuUsers, LuUser, LuInfo, LuShieldCheck, LuClock } from "react-icons/lu";
|
||
import type { MatchResponseDto } from "@/lib/api/matches/types";
|
||
import type { MatchPredictionDto } from "@/lib/api/predictions/types";
|
||
|
||
interface LineupsCardProps {
|
||
match: MatchResponseDto;
|
||
prediction?: MatchPredictionDto | null;
|
||
}
|
||
|
||
/**
|
||
* Lineup source metadata used for title, badge, and informational banners.
|
||
*/
|
||
function getLineupSourceMeta(source?: string) {
|
||
switch (source) {
|
||
case "confirmed_live":
|
||
return {
|
||
title: "Resmi İlk 11",
|
||
badge: "Onaylı Kadro",
|
||
badgeColor: "green" as const,
|
||
icon: LuShieldCheck,
|
||
description: "Kadro resmi olarak onaylandı.",
|
||
};
|
||
case "confirmed_participation":
|
||
return {
|
||
title: "Onaylı Kadro",
|
||
badge: "Onaylı",
|
||
badgeColor: "green" as const,
|
||
icon: LuShieldCheck,
|
||
description: "Kadro maç katılım verilerinden alındı.",
|
||
};
|
||
case "probable_xi":
|
||
return {
|
||
title: "Muhtemel Kadro",
|
||
badge: "Muhtemel",
|
||
badgeColor: "orange" as const,
|
||
icon: LuUsers,
|
||
description:
|
||
"Son maçlardaki ilk 11 verilerine dayalı muhtemel kadro. AI analizi bu kadro üzerinden yapılmaktadır.",
|
||
};
|
||
case "none":
|
||
default:
|
||
return {
|
||
title: "Kadro Bilgisi",
|
||
badge: "Kadro Bekleniyor",
|
||
badgeColor: "gray" as const,
|
||
icon: LuClock,
|
||
description:
|
||
"Kadro henüz açıklanmadı. AI analizi, takımların genel güç dengesi ve istatistiklerine dayalı olarak üretilmiştir.",
|
||
};
|
||
}
|
||
}
|
||
|
||
export default function LineupsCard({ match, prediction }: LineupsCardProps) {
|
||
const cardBg = useColorModeValue("white", "gray.800");
|
||
const borderColor = useColorModeValue("gray.100", "gray.700");
|
||
const headerBg = useColorModeValue("gray.50", "whiteAlpha.50");
|
||
const infoBg = useColorModeValue("blue.50", "whiteAlpha.100");
|
||
const infoBorder = useColorModeValue("blue.200", "blue.800");
|
||
|
||
let homeLineups = match.lineups?.home?.filter((p) => p.isStarting) || [];
|
||
let awayLineups = match.lineups?.away?.filter((p) => p.isStarting) || [];
|
||
|
||
// Determine lineup source from prediction data quality
|
||
const source = prediction?.data_quality?.lineup_source;
|
||
const meta = getLineupSourceMeta(source);
|
||
|
||
// Fallback: If no starting players are marked, but we have players, treat them as probable XI
|
||
if (homeLineups.length === 0 && match.lineups?.home && match.lineups.home.length > 0) {
|
||
homeLineups = match.lineups.home.slice(0, 11);
|
||
}
|
||
if (awayLineups.length === 0 && match.lineups?.away && match.lineups.away.length > 0) {
|
||
awayLineups = match.lineups.away.slice(0, 11);
|
||
}
|
||
|
||
const hasLineups = homeLineups.length > 0 || awayLineups.length > 0;
|
||
|
||
return (
|
||
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="xl" mb={6}>
|
||
<Card.Body>
|
||
{/* ── Header ────────────────────────────────── */}
|
||
<Flex justify="space-between" align="center" mb={4}>
|
||
<HStack gap={2}>
|
||
<Icon as={meta.icon} boxSize={5} color="fg.muted" />
|
||
<Text fontSize="lg" fontWeight="semibold">
|
||
{meta.title}
|
||
</Text>
|
||
</HStack>
|
||
<Badge
|
||
colorPalette={meta.badgeColor}
|
||
variant="subtle"
|
||
>
|
||
{meta.badge}
|
||
</Badge>
|
||
</Flex>
|
||
|
||
{/* ── Info Banner ───────────────────────────── */}
|
||
{source !== "confirmed_live" && (
|
||
<Flex
|
||
bg={infoBg}
|
||
borderWidth="1px"
|
||
borderColor={infoBorder}
|
||
borderRadius="md"
|
||
p={3}
|
||
mb={4}
|
||
align="center"
|
||
gap={2}
|
||
>
|
||
<Icon as={LuInfo} color="blue.500" flexShrink={0} />
|
||
<Text fontSize="xs" color="fg.muted">
|
||
{meta.description}
|
||
</Text>
|
||
</Flex>
|
||
)}
|
||
|
||
{/* ── Lineups Grid ─────────────────────────── */}
|
||
{hasLineups ? (
|
||
<SimpleGrid columns={{ base: 1, md: 2 }} gap={6}>
|
||
{/* Home Team Lineup */}
|
||
<Box>
|
||
<Flex
|
||
bg={headerBg}
|
||
p={3}
|
||
borderRadius="md"
|
||
align="center"
|
||
justify="center"
|
||
mb={3}
|
||
gap={2}
|
||
>
|
||
<Text fontWeight="bold">{match.homeTeamName}</Text>
|
||
<Badge size="sm" variant="outline" colorPalette="blue">
|
||
Ev Sahibi
|
||
</Badge>
|
||
</Flex>
|
||
{homeLineups.length > 0 ? (
|
||
<VStack align="stretch" gap={2}>
|
||
{homeLineups.map((p, idx) => (
|
||
<HStack
|
||
key={p.player?.id || idx}
|
||
p={2}
|
||
borderWidth="1px"
|
||
borderColor={borderColor}
|
||
borderRadius="md"
|
||
>
|
||
<Icon as={LuUser} color="fg.muted" />
|
||
{p.shirtNumber && (
|
||
<Text fontSize="xs" fontWeight="bold" w="20px">
|
||
{p.shirtNumber}
|
||
</Text>
|
||
)}
|
||
<Text fontSize="sm" fontWeight="medium">
|
||
{p.player?.name || "Bilinmiyor"}
|
||
</Text>
|
||
{p.position && (
|
||
<Badge ml="auto" size="sm" variant="surface">
|
||
{p.position}
|
||
</Badge>
|
||
)}
|
||
</HStack>
|
||
))}
|
||
</VStack>
|
||
) : (
|
||
<Flex
|
||
p={4}
|
||
borderWidth="1px"
|
||
borderColor={borderColor}
|
||
borderRadius="md"
|
||
justify="center"
|
||
align="center"
|
||
direction="column"
|
||
gap={1}
|
||
>
|
||
<Text fontSize="sm" color="fg.muted" fontWeight="medium">
|
||
Kadro henüz belli değil
|
||
</Text>
|
||
<Text fontSize="xs" color="fg.subtle">
|
||
Maç saatine yakın güncellenecek
|
||
</Text>
|
||
</Flex>
|
||
)}
|
||
</Box>
|
||
|
||
{/* Away Team Lineup */}
|
||
<Box>
|
||
<Flex
|
||
bg={headerBg}
|
||
p={3}
|
||
borderRadius="md"
|
||
align="center"
|
||
justify="center"
|
||
mb={3}
|
||
gap={2}
|
||
>
|
||
<Text fontWeight="bold">{match.awayTeamName}</Text>
|
||
<Badge size="sm" variant="outline" colorPalette="red">
|
||
Deplasman
|
||
</Badge>
|
||
</Flex>
|
||
{awayLineups.length > 0 ? (
|
||
<VStack align="stretch" gap={2}>
|
||
{awayLineups.map((p, idx) => (
|
||
<HStack
|
||
key={p.player?.id || idx}
|
||
p={2}
|
||
borderWidth="1px"
|
||
borderColor={borderColor}
|
||
borderRadius="md"
|
||
>
|
||
<Icon as={LuUser} color="fg.muted" />
|
||
{p.shirtNumber && (
|
||
<Text fontSize="xs" fontWeight="bold" w="20px">
|
||
{p.shirtNumber}
|
||
</Text>
|
||
)}
|
||
<Text fontSize="sm" fontWeight="medium">
|
||
{p.player?.name || "Bilinmiyor"}
|
||
</Text>
|
||
{p.position && (
|
||
<Badge ml="auto" size="sm" variant="surface">
|
||
{p.position}
|
||
</Badge>
|
||
)}
|
||
</HStack>
|
||
))}
|
||
</VStack>
|
||
) : (
|
||
<Flex
|
||
p={4}
|
||
borderWidth="1px"
|
||
borderColor={borderColor}
|
||
borderRadius="md"
|
||
justify="center"
|
||
align="center"
|
||
direction="column"
|
||
gap={1}
|
||
>
|
||
<Text fontSize="sm" color="fg.muted" fontWeight="medium">
|
||
Kadro henüz belli değil
|
||
</Text>
|
||
<Text fontSize="xs" color="fg.subtle">
|
||
Maç saatine yakın güncellenecek
|
||
</Text>
|
||
</Flex>
|
||
)}
|
||
</Box>
|
||
</SimpleGrid>
|
||
) : (
|
||
/* ── Empty State: No lineups at all ─────── */
|
||
<Flex
|
||
direction="column"
|
||
align="center"
|
||
justify="center"
|
||
py={8}
|
||
gap={3}
|
||
>
|
||
<Icon as={LuClock} boxSize={8} color="fg.subtle" />
|
||
<VStack gap={1}>
|
||
<Text fontWeight="semibold" color="fg.muted">
|
||
Kadro Henüz Açıklanmadı
|
||
</Text>
|
||
<Text fontSize="sm" color="fg.subtle" textAlign="center" maxW="sm">
|
||
{match.homeTeamName} ve {match.awayTeamName} kadroları maç saatine
|
||
yakın güncellenecektir. AI analizi, takım istatistikleri ve güç
|
||
dengesi üzerinden yapılmaktadır.
|
||
</Text>
|
||
</VStack>
|
||
</Flex>
|
||
)}
|
||
</Card.Body>
|
||
</Card.Root>
|
||
);
|
||
}
|