This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user