first
Deploy Iddaai Frontend / build-and-deploy (push) Successful in 4m0s

This commit is contained in:
2026-04-16 13:36:34 +03:00
parent de5e145c4e
commit fc7a1ba567
218 changed files with 32370 additions and 0 deletions
@@ -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>
);
}