Files
iddaai-fe/src/components/matches/lineups-card.tsx
T
2026-04-24 23:46:50 +03:00

286 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}