165 lines
7.2 KiB
TypeScript
165 lines
7.2 KiB
TypeScript
'use client';
|
|
import { Link } from '@/i18n/navigation';
|
|
|
|
import { Box, Grid, Heading, Text, VStack, Badge, Flex, Image as ChakraImage, SimpleGrid } from '@chakra-ui/react';
|
|
import { useState } from 'react';
|
|
import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay, addMonths, subMonths, getDay } from 'date-fns';
|
|
import { enUS, tr } from 'date-fns/locale';
|
|
import { useLocale } from 'next-intl';
|
|
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
|
|
import { IconButton } from '@chakra-ui/react';
|
|
import { FilterBar, FilterState } from './filter-bar';
|
|
// import { Game, Event } from '@prisma/client'; // Removed dependency
|
|
// Note: In a real monorepo we'd share types. For now we will mock or use any.
|
|
|
|
interface CalendarProps {
|
|
games: any[]; // Replace with correct type
|
|
events: any[];
|
|
}
|
|
|
|
export function GameCalendar({ games = [], events = [] }: CalendarProps) {
|
|
const [currentDate, setCurrentDate] = useState(new Date());
|
|
const [filters, setFilters] = useState<FilterState>({
|
|
search: '',
|
|
platforms: ['PC', 'PS5', 'Xbox', 'Switch'],
|
|
showEvents: true
|
|
});
|
|
const locale = useLocale();
|
|
const dateLocale = locale === 'tr' ? tr : enUS;
|
|
|
|
const monthStart = startOfMonth(currentDate);
|
|
const monthEnd = endOfMonth(monthStart);
|
|
const daysInMonth = eachDayOfInterval({ start: monthStart, end: monthEnd });
|
|
|
|
// Add padding days for start of month
|
|
const startDayOfWeek = getDay(monthStart); // 0 (Sun) to 6 (Sat)
|
|
// Adjust for Monday start if needed. Let's assume standard Sunday start for grid.
|
|
const paddingDays = Array.from({ length: startDayOfWeek });
|
|
|
|
const nextMonth = () => setCurrentDate(addMonths(currentDate, 1));
|
|
const prevMonth = () => setCurrentDate(subMonths(currentDate, 1));
|
|
|
|
const getItemsForDay = (date: Date) => {
|
|
// Filter Games
|
|
const dayGames = games.filter(g => {
|
|
if (!g.releaseDate || !isSameDay(new Date(g.releaseDate), date)) return false;
|
|
|
|
// Search filter
|
|
if (filters.search && !g.title.toLowerCase().includes(filters.search.toLowerCase())) return false;
|
|
|
|
// Platform filter (if game has platforms)
|
|
if (g.platforms && g.platforms.length > 0) {
|
|
const hasPlatform = g.platforms.some((p: string) => filters.platforms.includes(p) || filters.platforms.some(fp => p.includes(fp)));
|
|
if (!hasPlatform) return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Filter Events
|
|
const dayEvents = filters.showEvents ? events.filter(e => {
|
|
if (!isSameDay(new Date(e.startTime), date)) return false;
|
|
if (filters.search && !e.title.toLowerCase().includes(filters.search.toLowerCase())) return false;
|
|
return true;
|
|
}) : [];
|
|
|
|
return [...dayGames.map(g => ({ ...g, type: 'game' })), ...dayEvents.map(e => ({ ...e, type: 'event' }))];
|
|
};
|
|
|
|
return (
|
|
<Box w="full" bg="whiteAlpha.300" rounded="xl" p={6} backdropFilter="blur(10px)" border="1px solid" borderColor="whiteAlpha.200">
|
|
{/* Header */}
|
|
<Flex justify="space-between" align="center" mb={6}>
|
|
<Heading size="lg" color="white">
|
|
{format(currentDate, 'MMMM yyyy', { locale: dateLocale })}
|
|
</Heading>
|
|
<Flex gap={2}>
|
|
<IconButton aria-label="Previous month" onClick={prevMonth} variant="ghost" color="white">
|
|
<FaChevronLeft />
|
|
</IconButton>
|
|
<IconButton aria-label="Next month" onClick={nextMonth} variant="ghost" color="white">
|
|
<FaChevronRight />
|
|
</IconButton>
|
|
</Flex>
|
|
</Flex>
|
|
|
|
{/* Filters */}
|
|
<FilterBar filters={filters} onFilterChange={setFilters} />
|
|
|
|
{/* Days Header */}
|
|
<SimpleGrid columns={7} mb={2}>
|
|
{['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => (
|
|
<Text key={day} textAlign="center" color="gray.400" fontWeight="bold" fontSize="sm">
|
|
{day}
|
|
</Text>
|
|
))}
|
|
</SimpleGrid>
|
|
|
|
{/* Calendar Grid */}
|
|
<SimpleGrid columns={7} gap={1} minH="500px">
|
|
{paddingDays.map((_, i) => (
|
|
<Box key={`padding-${i}`} bg="transparent" />
|
|
))}
|
|
|
|
{daysInMonth.map((date) => {
|
|
const items = getItemsForDay(date);
|
|
const isToday = isSameDay(date, new Date());
|
|
|
|
return (
|
|
<Box
|
|
key={date.toString()}
|
|
bg={isToday ? 'primary.900' : 'whiteAlpha.200'}
|
|
border="1px solid"
|
|
borderColor={isToday ? 'primary.500' : 'whiteAlpha.100'}
|
|
rounded="md"
|
|
p={2}
|
|
minH="100px"
|
|
transition="all 0.2s"
|
|
_hover={{ bg: 'whiteAlpha.200', transform: 'scale(1.02)', zIndex: 1 }}
|
|
cursor="pointer"
|
|
position="relative"
|
|
overflow="hidden"
|
|
>
|
|
<Text fontSize="sm" color={isToday ? 'primary.300' : 'gray.400'} mb={1} fontWeight={isToday ? 'bold' : 'normal'}>
|
|
{format(date, 'd')}
|
|
</Text>
|
|
|
|
<VStack align="stretch" gap={1}>
|
|
{items.map((item: any, idx) => {
|
|
const badge = (
|
|
<Badge
|
|
key={`${item.id}-${idx}`}
|
|
size="sm"
|
|
variant="solid"
|
|
colorPalette={item.type === 'game' ? 'blue' : 'purple'}
|
|
truncate
|
|
fontSize="xs"
|
|
px={1}
|
|
cursor="pointer"
|
|
_hover={{ opacity: 0.8 }}
|
|
>
|
|
{item.title}
|
|
</Badge>
|
|
);
|
|
|
|
if (item.type === 'game') {
|
|
return (
|
|
<Link key={`${item.id}-${idx}`} href={`/games/${item.slug}`}>
|
|
{badge}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
return badge;
|
|
})}
|
|
</VStack>
|
|
|
|
{/* Background image effect for heavy days? Optional polish */}
|
|
</Box>
|
|
);
|
|
})}
|
|
</SimpleGrid>
|
|
</Box >
|
|
);
|
|
}
|