This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Heading,
|
||||
Text,
|
||||
Card,
|
||||
VStack,
|
||||
HStack,
|
||||
Badge,
|
||||
Spinner,
|
||||
Input,
|
||||
Tabs,
|
||||
} from "@chakra-ui/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useColorModeValue } from "@/components/ui/color-mode";
|
||||
import { SlideUp } from "@/components/motion";
|
||||
import {
|
||||
useCountries,
|
||||
useLeagues,
|
||||
useSearchTeams,
|
||||
} from "@/lib/api/leagues/use-hooks";
|
||||
import type { CountryDto, LeagueDto, TeamDto } from "@/lib/api/leagues/types";
|
||||
import { LuSearch, LuGlobe, LuTrophy, LuUsers } from "react-icons/lu";
|
||||
import { useState } from "react";
|
||||
import { useDebounce } from "@/hooks/use-debounce";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { InputGroup } from "@/components/ui/forms/input-group";
|
||||
import { Link as ChakraLink } from "@chakra-ui/react";
|
||||
|
||||
export default function LeaguesContent() {
|
||||
const t = useTranslations("leagues");
|
||||
const tMatches = useTranslations("matches");
|
||||
|
||||
const cardBg = useColorModeValue("white", "gray.800");
|
||||
const borderColor = useColorModeValue("gray.100", "gray.700");
|
||||
|
||||
const [activeTab, setActiveTab] = useState<"leagues" | "teams">("leagues");
|
||||
const [sportFilter, setSportFilter] = useState<string>("");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const debouncedQuery = useDebounce(searchQuery, 300);
|
||||
|
||||
const countries = useCountries();
|
||||
const leagues = useLeagues(
|
||||
sportFilter
|
||||
? { sport: sportFilter as "football" | "basketball" }
|
||||
: undefined,
|
||||
);
|
||||
const searchTeams = useSearchTeams(
|
||||
debouncedQuery.length >= 2 ? { q: debouncedQuery } : { q: "" },
|
||||
);
|
||||
|
||||
return (
|
||||
<SlideUp>
|
||||
<Box maxW="6xl" mx="auto">
|
||||
<Heading as="h1" size="xl" fontWeight="bold" mb={6}>
|
||||
{t("title")}
|
||||
</Heading>
|
||||
|
||||
<Tabs.Root
|
||||
value={activeTab}
|
||||
onValueChange={(e) => setActiveTab(e.value as "leagues" | "teams")}
|
||||
>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="leagues">
|
||||
<LuGlobe />
|
||||
{t("countries-leagues")}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="teams">
|
||||
<LuUsers />
|
||||
{tMatches("search-teams")}
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
{/* Countries & Leagues Tab */}
|
||||
<Tabs.Content value="leagues">
|
||||
<Flex gap={6} direction={{ base: "column", lg: "row" }}>
|
||||
{/* Countries Sidebar */}
|
||||
<Box w={{ base: "full", lg: "280px" }} flexShrink={0}>
|
||||
<Card.Root
|
||||
bg={cardBg}
|
||||
borderColor={borderColor}
|
||||
borderRadius="xl"
|
||||
>
|
||||
<Card.Header>
|
||||
<Heading as="h4" size="sm">
|
||||
<HStack gap={2}>
|
||||
<LuGlobe />
|
||||
<Text>{t("countries")}</Text>
|
||||
</HStack>
|
||||
</Heading>
|
||||
</Card.Header>
|
||||
<Card.Body pt={0} maxH="600px" overflowY="auto">
|
||||
{countries.isLoading ? (
|
||||
<Flex justify="center" py={4}>
|
||||
<Spinner size="sm" />
|
||||
</Flex>
|
||||
) : (
|
||||
<VStack gap={1} align="stretch">
|
||||
{countries.data?.data?.map((country: CountryDto) => (
|
||||
<Flex
|
||||
key={country.id}
|
||||
px={3}
|
||||
py={2}
|
||||
borderRadius="md"
|
||||
_hover={{
|
||||
bg: "gray.50",
|
||||
_dark: { bg: "gray.750" },
|
||||
}}
|
||||
cursor="pointer"
|
||||
justify="space-between"
|
||||
align="center"
|
||||
>
|
||||
<HStack gap={2}>
|
||||
{country.flag ? (
|
||||
<img
|
||||
src={country.flag}
|
||||
width="16"
|
||||
height="16"
|
||||
style={{ borderRadius: "2px" }}
|
||||
alt={country.name}
|
||||
/>
|
||||
) : null}
|
||||
<Text fontSize="sm">{country.name}</Text>
|
||||
</HStack>
|
||||
<Badge size="xs" colorScheme="gray">
|
||||
{country.leagues?.length || 0}
|
||||
</Badge>
|
||||
</Flex>
|
||||
))}
|
||||
</VStack>
|
||||
)}
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
</Box>
|
||||
|
||||
{/* Leagues List */}
|
||||
<Box flex={1}>
|
||||
<Card.Root
|
||||
bg={cardBg}
|
||||
borderColor={borderColor}
|
||||
borderRadius="xl"
|
||||
>
|
||||
<Card.Header>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Heading as="h4" size="sm">
|
||||
<HStack gap={2}>
|
||||
<LuTrophy />
|
||||
<Text>{t("leagues")}</Text>
|
||||
</HStack>
|
||||
</Heading>
|
||||
<HStack gap={2}>
|
||||
<Badge
|
||||
cursor="pointer"
|
||||
colorScheme={!sportFilter ? "primary" : "gray"}
|
||||
onClick={() => setSportFilter("")}
|
||||
>
|
||||
{tMatches("all")}
|
||||
</Badge>
|
||||
<Badge
|
||||
cursor="pointer"
|
||||
colorScheme={
|
||||
sportFilter === "football" ? "green" : "gray"
|
||||
}
|
||||
onClick={() =>
|
||||
setSportFilter(
|
||||
sportFilter === "football" ? "" : "football",
|
||||
)
|
||||
}
|
||||
>
|
||||
{tMatches("football")}
|
||||
</Badge>
|
||||
<Badge
|
||||
cursor="pointer"
|
||||
colorScheme={
|
||||
sportFilter === "basketball" ? "orange" : "gray"
|
||||
}
|
||||
onClick={() =>
|
||||
setSportFilter(
|
||||
sportFilter === "basketball" ? "" : "basketball",
|
||||
)
|
||||
}
|
||||
>
|
||||
{tMatches("basketball")}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Card.Header>
|
||||
<Card.Body pt={0}>
|
||||
{leagues.isLoading ? (
|
||||
<Flex justify="center" py={6}>
|
||||
<Spinner size="sm" />
|
||||
</Flex>
|
||||
) : (
|
||||
<VStack gap={2}>
|
||||
{leagues.data?.data?.map((league: LeagueDto) => (
|
||||
<ChakraLink
|
||||
key={league.id}
|
||||
as={Link}
|
||||
href="/matches"
|
||||
p={3}
|
||||
borderRadius="md"
|
||||
borderWidth="1px"
|
||||
borderColor={borderColor}
|
||||
_hover={{
|
||||
borderColor: "primary.300",
|
||||
bg: "primary.50",
|
||||
_dark: { bg: "gray.750" },
|
||||
}}
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
textDecoration="none"
|
||||
color="inherit"
|
||||
>
|
||||
<VStack align="start" gap={0}>
|
||||
<Text fontWeight="semibold">{league.name}</Text>
|
||||
<Text fontSize="xs" color="fg.muted">
|
||||
{league.country?.name || ""}
|
||||
</Text>
|
||||
</VStack>
|
||||
<HStack gap={2}>
|
||||
{league.sport ? (
|
||||
<Badge
|
||||
size="xs"
|
||||
colorScheme={
|
||||
league.sport === "football"
|
||||
? "green"
|
||||
: "orange"
|
||||
}
|
||||
>
|
||||
{league.sport}
|
||||
</Badge>
|
||||
) : null}
|
||||
{league.season ? (
|
||||
<Text fontSize="xs" color="fg.muted">
|
||||
{league.season}
|
||||
</Text>
|
||||
) : null}
|
||||
</HStack>
|
||||
</ChakraLink>
|
||||
))}
|
||||
</VStack>
|
||||
)}
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Tabs.Content>
|
||||
|
||||
{/* Teams Search Tab */}
|
||||
<Tabs.Content value="teams">
|
||||
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="xl">
|
||||
<Card.Body>
|
||||
<InputGroup startElement={<LuSearch />} mb={4}>
|
||||
<Input
|
||||
placeholder={tMatches("search-teams")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
{debouncedQuery.length < 2 ? (
|
||||
<Text color="fg.muted" textAlign="center" py={8}>
|
||||
{t("search-at-least-2")}
|
||||
</Text>
|
||||
) : searchTeams.isLoading ? (
|
||||
<Flex justify="center" py={6}>
|
||||
<Spinner size="md" />
|
||||
</Flex>
|
||||
) : (
|
||||
<VStack gap={2}>
|
||||
{searchTeams.data?.data?.map((team: TeamDto) => (
|
||||
<ChakraLink
|
||||
key={team.id}
|
||||
as={Link}
|
||||
href={`/teams/${team.id}`}
|
||||
p={3}
|
||||
borderRadius="md"
|
||||
borderWidth="1px"
|
||||
borderColor={borderColor}
|
||||
_hover={{
|
||||
borderColor: "primary.300",
|
||||
bg: "primary.50",
|
||||
_dark: { bg: "gray.750" },
|
||||
}}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={3}
|
||||
textDecoration="none"
|
||||
color="inherit"
|
||||
>
|
||||
{team.logo ? (
|
||||
<img
|
||||
src={team.logo}
|
||||
width="32"
|
||||
height="32"
|
||||
style={{ borderRadius: "50%" }}
|
||||
alt={team.name}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
boxSize="32px"
|
||||
borderRadius="full"
|
||||
bg="gray.200"
|
||||
_dark={{ bg: "gray.600" }}
|
||||
/>
|
||||
)}
|
||||
<VStack align="start" gap={0}>
|
||||
<Text fontWeight="semibold">{team.name}</Text>
|
||||
<Text fontSize="xs" color="fg.muted">
|
||||
{team.country || ""}
|
||||
</Text>
|
||||
</VStack>
|
||||
<Badge
|
||||
ml="auto"
|
||||
size="xs"
|
||||
colorScheme={
|
||||
team.sport === "football" ? "green" : "orange"
|
||||
}
|
||||
>
|
||||
{team.sport}
|
||||
</Badge>
|
||||
</ChakraLink>
|
||||
))}
|
||||
</VStack>
|
||||
)}
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</Box>
|
||||
</SlideUp>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user