|
| 1 | +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; |
| 2 | +import { useMemo } from "react"; |
| 3 | +import { |
| 4 | + calculateYearMaxCount, |
| 5 | + filterDataByYear, |
| 6 | + generateMonthsForYear, |
| 7 | + getMonthLabel, |
| 8 | + MonthCalendar, |
| 9 | +} from "@/components/ActivityCalendar"; |
| 10 | +import { Button } from "@/components/ui/button"; |
| 11 | +import { TooltipProvider } from "@/components/ui/tooltip"; |
| 12 | +import { cn } from "@/lib/utils"; |
| 13 | +import { useTranslate } from "@/utils/i18n"; |
| 14 | +import { getMaxYear, MIN_YEAR } from "./constants"; |
| 15 | +import type { YearCalendarProps } from "./types"; |
| 16 | + |
| 17 | +export const YearCalendar = ({ selectedYear, data, onYearChange, onDateClick, className }: YearCalendarProps) => { |
| 18 | + const t = useTranslate(); |
| 19 | + const currentYear = useMemo(() => new Date().getFullYear(), []); |
| 20 | + const yearData = useMemo(() => filterDataByYear(data, selectedYear), [data, selectedYear]); |
| 21 | + const months = useMemo(() => generateMonthsForYear(selectedYear), [selectedYear]); |
| 22 | + const yearMaxCount = useMemo(() => calculateYearMaxCount(yearData), [yearData]); |
| 23 | + |
| 24 | + const canGoPrev = selectedYear > MIN_YEAR; |
| 25 | + const canGoNext = selectedYear < getMaxYear(); |
| 26 | + const isCurrentYear = selectedYear === currentYear; |
| 27 | + |
| 28 | + const handlePrevYear = () => canGoPrev && onYearChange(selectedYear - 1); |
| 29 | + const handleNextYear = () => canGoNext && onYearChange(selectedYear + 1); |
| 30 | + const handleToday = () => onYearChange(currentYear); |
| 31 | + |
| 32 | + return ( |
| 33 | + <div className={cn("w-full flex flex-col gap-6 p-2 md:p-0 select-none", className)}> |
| 34 | + <div className="flex items-center justify-between pb-4 px-2 pt-2"> |
| 35 | + <h2 className="text-3xl font-bold text-foreground tracking-tight leading-none">{selectedYear}</h2> |
| 36 | + |
| 37 | + <div className="inline-flex items-center gap-1 shrink-0"> |
| 38 | + <Button |
| 39 | + variant="ghost" |
| 40 | + size="sm" |
| 41 | + onClick={handlePrevYear} |
| 42 | + disabled={!canGoPrev} |
| 43 | + aria-label="Previous year" |
| 44 | + className="h-9 w-9 p-0 rounded-md hover:bg-secondary/80 text-muted-foreground hover:text-foreground" |
| 45 | + > |
| 46 | + <ChevronLeftIcon className="w-5 h-5" /> |
| 47 | + </Button> |
| 48 | + |
| 49 | + <Button |
| 50 | + variant="ghost" |
| 51 | + size="sm" |
| 52 | + onClick={handleToday} |
| 53 | + disabled={isCurrentYear} |
| 54 | + aria-label={t("common.today")} |
| 55 | + className={cn( |
| 56 | + "h-9 px-4 rounded-md text-sm font-medium transition-colors", |
| 57 | + isCurrentYear ? "bg-secondary/50 text-muted-foreground cursor-default" : "hover:bg-secondary/80 text-foreground", |
| 58 | + )} |
| 59 | + > |
| 60 | + {t("common.today")} |
| 61 | + </Button> |
| 62 | + |
| 63 | + <Button |
| 64 | + variant="ghost" |
| 65 | + size="sm" |
| 66 | + onClick={handleNextYear} |
| 67 | + disabled={!canGoNext} |
| 68 | + aria-label="Next year" |
| 69 | + className="h-9 w-9 p-0 rounded-md hover:bg-secondary/80 text-muted-foreground hover:text-foreground" |
| 70 | + > |
| 71 | + <ChevronRightIcon className="w-5 h-5" /> |
| 72 | + </Button> |
| 73 | + </div> |
| 74 | + </div> |
| 75 | + |
| 76 | + <TooltipProvider> |
| 77 | + <div className="w-full animate-fade-in"> |
| 78 | + <div className="grid gap-6 grid-cols-1 sm:grid-cols-2 md:grid-cols-3"> |
| 79 | + {months.map((month) => ( |
| 80 | + <div |
| 81 | + key={month} |
| 82 | + className="flex flex-col gap-3 rounded-lg p-3 hover:bg-secondary/40 transition-colors cursor-default border border-transparent hover:border-border/50" |
| 83 | + > |
| 84 | + <div className="text-xs font-bold text-foreground/80 uppercase tracking-widest pl-1">{getMonthLabel(month)}</div> |
| 85 | + <MonthCalendar month={month} data={yearData} maxCount={yearMaxCount} size="small" onClick={onDateClick} /> |
| 86 | + </div> |
| 87 | + ))} |
| 88 | + </div> |
| 89 | + </div> |
| 90 | + </TooltipProvider> |
| 91 | + </div> |
| 92 | + ); |
| 93 | +}; |
0 commit comments