233 lines
7.9 KiB
TypeScript
233 lines
7.9 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Box,
|
|
Flex,
|
|
Heading,
|
|
Text,
|
|
Card,
|
|
VStack,
|
|
HStack,
|
|
Badge,
|
|
Spinner,
|
|
Button,
|
|
SimpleGrid,
|
|
} from "@chakra-ui/react";
|
|
import { useTranslations } from "next-intl";
|
|
import { useColorModeValue } from "@/components/ui/color-mode";
|
|
import { SlideUp } from "@/components/motion";
|
|
import {
|
|
useAnalyzeMatches,
|
|
useAnalysisHistory,
|
|
} from "@/lib/api/analysis/use-hooks";
|
|
import { useQueryMatches } from "@/lib/api/matches/use-hooks";
|
|
import type { LeagueWithMatchesDto } from "@/lib/api/matches/types";
|
|
import { LuSparkles, LuClock, LuCheck } from "react-icons/lu";
|
|
import { useState } from "react";
|
|
import { toaster } from "@/components/ui/feedback/toaster";
|
|
|
|
export default function AnalysisContent() {
|
|
const t = useTranslations("analysis");
|
|
const tCommon = useTranslations("common");
|
|
|
|
const cardBg = useColorModeValue("white", "gray.800");
|
|
const borderColor = useColorModeValue("gray.100", "gray.700");
|
|
|
|
const [selectedMatchIds, setSelectedMatchIds] = useState<string[]>([]);
|
|
|
|
const upcomingMatches = useQueryMatches();
|
|
const analyzeMutation = useAnalyzeMatches();
|
|
const historyQuery = useAnalysisHistory();
|
|
const toast = (opts: { title: string; status: string }) =>
|
|
toaster.create({
|
|
title: opts.title,
|
|
type: opts.status as "success" | "warning" | "error" | "info" | "loading",
|
|
});
|
|
|
|
const toggleMatch = (id: string) => {
|
|
setSelectedMatchIds((prev) =>
|
|
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
|
|
);
|
|
};
|
|
|
|
const handleAnalyze = async () => {
|
|
if (selectedMatchIds.length < 2) {
|
|
toast({
|
|
title: t("select-at-least-2"),
|
|
status: "warning",
|
|
});
|
|
return;
|
|
}
|
|
await analyzeMutation.mutateAsync({ matchIds: selectedMatchIds });
|
|
toast({
|
|
title: t("analysis-complete"),
|
|
status: "success",
|
|
});
|
|
historyQuery.refetch();
|
|
};
|
|
|
|
const allMatches: { id: string; home: string; away: string; date: string }[] =
|
|
upcomingMatches.data?.data
|
|
?.flatMap((league: LeagueWithMatchesDto) =>
|
|
league.matches?.map((m) => ({
|
|
id: m.id,
|
|
home: m.homeTeam?.name || "",
|
|
away: m.awayTeam?.name || "",
|
|
date: m.mstUtc ? new Date(m.mstUtc).toLocaleDateString() : "",
|
|
})),
|
|
)
|
|
.filter(Boolean) || [];
|
|
|
|
return (
|
|
<SlideUp>
|
|
<Box maxW="6xl" mx="auto">
|
|
<Heading as="h1" size="xl" fontWeight="bold" mb={6}>
|
|
{t("title")}
|
|
</Heading>
|
|
|
|
<Flex gap={6} direction={{ base: "column", lg: "row" }}>
|
|
{/* Match Selection */}
|
|
<Box flex={2}>
|
|
<Card.Root
|
|
bg={cardBg}
|
|
borderColor={borderColor}
|
|
borderRadius="xl"
|
|
mb={6}
|
|
>
|
|
<Card.Header>
|
|
<Flex justify="space-between" align="center">
|
|
<Heading as="h3" size="sm">
|
|
{t("select-matches")}
|
|
</Heading>
|
|
<Badge
|
|
colorScheme={
|
|
selectedMatchIds.length > 0 ? "primary" : "gray"
|
|
}
|
|
>
|
|
{selectedMatchIds.length} {t("selected")}
|
|
</Badge>
|
|
</Flex>
|
|
</Card.Header>
|
|
<Card.Body pt={0}>
|
|
{upcomingMatches.isPending ? (
|
|
<Flex justify="center" py={6}>
|
|
<Spinner size="sm" />
|
|
</Flex>
|
|
) : (
|
|
<VStack gap={2}>
|
|
{allMatches.map((m) => {
|
|
const isSelected = selectedMatchIds.includes(m.id);
|
|
return (
|
|
<Flex
|
|
key={m.id}
|
|
p={3}
|
|
borderRadius="md"
|
|
borderWidth="1px"
|
|
borderColor={isSelected ? "primary.500" : borderColor}
|
|
bg={isSelected ? "primary.50" : "transparent"}
|
|
_dark={isSelected ? { bg: "primary.900" } : undefined}
|
|
justify="space-between"
|
|
align="center"
|
|
cursor="pointer"
|
|
onClick={() => toggleMatch(m.id)}
|
|
>
|
|
<HStack gap={3}>
|
|
<Box
|
|
boxSize="20px"
|
|
borderRadius="sm"
|
|
borderWidth="2px"
|
|
borderColor={
|
|
isSelected ? "primary.500" : "gray.300"
|
|
}
|
|
bg={isSelected ? "primary.500" : "transparent"}
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
color="white"
|
|
>
|
|
{isSelected ? <LuCheck size="12" /> : null}
|
|
</Box>
|
|
<Text fontSize="sm" fontWeight="medium">
|
|
{m.home} vs {m.away}
|
|
</Text>
|
|
</HStack>
|
|
<Text fontSize="xs" color="fg.muted">
|
|
{m.date}
|
|
</Text>
|
|
</Flex>
|
|
);
|
|
})}
|
|
</VStack>
|
|
)}
|
|
<Button
|
|
mt={4}
|
|
w="full"
|
|
onClick={handleAnalyze}
|
|
loading={analyzeMutation.isPending}
|
|
disabled={selectedMatchIds.length < 2}
|
|
>
|
|
<LuSparkles /> {t("analyze-matches")}
|
|
</Button>
|
|
</Card.Body>
|
|
</Card.Root>
|
|
</Box>
|
|
|
|
{/* Analysis History */}
|
|
<Box flex={1}>
|
|
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="xl">
|
|
<Card.Header>
|
|
<Heading as="h3" size="sm">
|
|
<HStack gap={2}>
|
|
<LuClock />
|
|
<Text>{t("history")}</Text>
|
|
</HStack>
|
|
</Heading>
|
|
</Card.Header>
|
|
<Card.Body pt={0}>
|
|
{historyQuery.isLoading ? (
|
|
<Flex justify="center" py={6}>
|
|
<Spinner size="sm" />
|
|
</Flex>
|
|
) : historyQuery.data?.data?.analyses &&
|
|
historyQuery.data.data.analyses.length > 0 ? (
|
|
<VStack gap={3}>
|
|
{historyQuery.data.data.analyses.map(
|
|
(a: {
|
|
id: string;
|
|
matchIds: string[];
|
|
createdAt: string;
|
|
}) => (
|
|
<Card.Root
|
|
key={a.id}
|
|
size="sm"
|
|
borderWidth="1px"
|
|
borderColor={borderColor}
|
|
>
|
|
<Card.Body>
|
|
<VStack align="start" gap={1}>
|
|
<Text fontSize="sm" fontWeight="semibold">
|
|
{a.matchIds.length} {t("matches-analyzed")}
|
|
</Text>
|
|
<Text fontSize="xs" color="fg.muted">
|
|
{new Date(a.createdAt).toLocaleString()}
|
|
</Text>
|
|
</VStack>
|
|
</Card.Body>
|
|
</Card.Root>
|
|
),
|
|
)}
|
|
</VStack>
|
|
) : (
|
|
<Text color="fg.muted" textAlign="center" py={6}>
|
|
{t("no-history")}
|
|
</Text>
|
|
)}
|
|
</Card.Body>
|
|
</Card.Root>
|
|
</Box>
|
|
</Flex>
|
|
</Box>
|
|
</SlideUp>
|
|
);
|
|
}
|