스타일 수정중
This commit is contained in:
263
frontend/components/ui/custom-calendar.tsx
Normal file
263
frontend/components/ui/custom-calendar.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
interface CustomCalendarProps {
|
||||
selected?: Date;
|
||||
onSelect?: (date: Date | undefined) => void;
|
||||
className?: string;
|
||||
mode?: "single";
|
||||
size?: "sm" | "default" | "lg";
|
||||
}
|
||||
|
||||
const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
||||
const MONTHS = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
|
||||
export function CustomCalendar({
|
||||
selected,
|
||||
onSelect,
|
||||
className,
|
||||
mode = "single",
|
||||
size = "default",
|
||||
}: CustomCalendarProps) {
|
||||
// 크기별 클래스 정의
|
||||
const sizeClasses = {
|
||||
sm: {
|
||||
cell: "h-7 w-7 text-xs",
|
||||
header: "text-xs",
|
||||
day: "text-[0.7rem]",
|
||||
nav: "h-6 w-6",
|
||||
},
|
||||
default: {
|
||||
cell: "h-9 w-9 text-sm",
|
||||
header: "text-[0.8rem]",
|
||||
day: "text-sm",
|
||||
nav: "h-7 w-7",
|
||||
},
|
||||
lg: {
|
||||
cell: "h-11 w-11 text-base",
|
||||
header: "text-sm",
|
||||
day: "text-base",
|
||||
nav: "h-8 w-8",
|
||||
},
|
||||
};
|
||||
|
||||
const currentSize = sizeClasses[size];
|
||||
const [currentDate, setCurrentDate] = React.useState(selected || new Date());
|
||||
const [viewYear, setViewYear] = React.useState(currentDate.getFullYear());
|
||||
const [viewMonth, setViewMonth] = React.useState(currentDate.getMonth());
|
||||
|
||||
const getDaysInMonth = (year: number, month: number) => {
|
||||
return new Date(year, month + 1, 0).getDate();
|
||||
};
|
||||
|
||||
const getFirstDayOfMonth = (year: number, month: number) => {
|
||||
return new Date(year, month, 1).getDay();
|
||||
};
|
||||
|
||||
const generateCalendarDays = () => {
|
||||
const daysInMonth = getDaysInMonth(viewYear, viewMonth);
|
||||
const firstDay = getFirstDayOfMonth(viewYear, viewMonth);
|
||||
const daysInPrevMonth = getDaysInMonth(viewYear, viewMonth - 1);
|
||||
|
||||
const days: Array<{
|
||||
date: number;
|
||||
month: "prev" | "current" | "next";
|
||||
fullDate: Date;
|
||||
}> = [];
|
||||
|
||||
// Previous month days
|
||||
for (let i = firstDay - 1; i >= 0; i--) {
|
||||
const date = daysInPrevMonth - i;
|
||||
days.push({
|
||||
date,
|
||||
month: "prev",
|
||||
fullDate: new Date(viewYear, viewMonth - 1, date),
|
||||
});
|
||||
}
|
||||
|
||||
// Current month days
|
||||
for (let i = 1; i <= daysInMonth; i++) {
|
||||
days.push({
|
||||
date: i,
|
||||
month: "current",
|
||||
fullDate: new Date(viewYear, viewMonth, i),
|
||||
});
|
||||
}
|
||||
|
||||
// Next month days
|
||||
const remainingDays = 42 - days.length; // 6 rows * 7 days
|
||||
for (let i = 1; i <= remainingDays; i++) {
|
||||
days.push({
|
||||
date: i,
|
||||
month: "next",
|
||||
fullDate: new Date(viewYear, viewMonth + 1, i),
|
||||
});
|
||||
}
|
||||
|
||||
return days;
|
||||
};
|
||||
|
||||
const handlePrevMonth = () => {
|
||||
if (viewMonth === 0) {
|
||||
setViewMonth(11);
|
||||
setViewYear(viewYear - 1);
|
||||
} else {
|
||||
setViewMonth(viewMonth - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextMonth = () => {
|
||||
if (viewMonth === 11) {
|
||||
setViewMonth(0);
|
||||
setViewYear(viewYear + 1);
|
||||
} else {
|
||||
setViewMonth(viewMonth + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateClick = (date: Date) => {
|
||||
if (onSelect) {
|
||||
onSelect(date);
|
||||
}
|
||||
};
|
||||
|
||||
const isToday = (date: Date) => {
|
||||
const today = new Date();
|
||||
return (
|
||||
date.getDate() === today.getDate() &&
|
||||
date.getMonth() === today.getMonth() &&
|
||||
date.getFullYear() === today.getFullYear()
|
||||
);
|
||||
};
|
||||
|
||||
const isSelected = (date: Date) => {
|
||||
if (!selected) return false;
|
||||
return (
|
||||
date.getDate() === selected.getDate() &&
|
||||
date.getMonth() === selected.getMonth() &&
|
||||
date.getFullYear() === selected.getFullYear()
|
||||
);
|
||||
};
|
||||
|
||||
const calendarDays = generateCalendarDays();
|
||||
|
||||
return (
|
||||
<div className={cn("p-3", className)}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 pb-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className={cn("bg-transparent p-0 opacity-50 hover:opacity-100", currentSize.nav)}
|
||||
onClick={handlePrevMonth}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 월 선택 */}
|
||||
<Select value={viewMonth.toString()} onValueChange={(value) => setViewMonth(parseInt(value))}>
|
||||
<SelectTrigger className={cn("w-[110px] font-medium", currentSize.header)}>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{MONTHS.map((month, index) => (
|
||||
<SelectItem key={index} value={index.toString()}>
|
||||
{month}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 연도 선택 */}
|
||||
<Select value={viewYear.toString()} onValueChange={(value) => setViewYear(parseInt(value))}>
|
||||
<SelectTrigger className={cn("w-[80px] font-medium", currentSize.header)}>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 100 }, (_, i) => {
|
||||
const year = new Date().getFullYear() - 50 + i;
|
||||
return (
|
||||
<SelectItem key={year} value={year.toString()}>
|
||||
{year}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className={cn("bg-transparent p-0 opacity-50 hover:opacity-100", currentSize.nav)}
|
||||
onClick={handleNextMonth}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Days of week */}
|
||||
<div className="mb-2 grid grid-cols-7 gap-0">
|
||||
{DAYS.map((day) => (
|
||||
<div
|
||||
key={day}
|
||||
className={cn(
|
||||
"text-muted-foreground flex items-center justify-center font-normal",
|
||||
currentSize.cell,
|
||||
currentSize.day,
|
||||
)}
|
||||
>
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Calendar grid */}
|
||||
<div className="grid grid-cols-7 gap-0">
|
||||
{calendarDays.map((day, index) => {
|
||||
const isOutside = day.month !== "current";
|
||||
const isTodayDate = isToday(day.fullDate);
|
||||
const isSelectedDate = isSelected(day.fullDate);
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={index}
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"p-0 font-normal",
|
||||
currentSize.cell,
|
||||
isOutside && "text-muted-foreground opacity-50",
|
||||
isTodayDate && !isSelectedDate && "bg-accent text-accent-foreground",
|
||||
isSelectedDate && "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground",
|
||||
)}
|
||||
onClick={() => handleDateClick(day.fullDate)}
|
||||
>
|
||||
{day.date}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CustomCalendar.displayName = "CustomCalendar";
|
||||
Reference in New Issue
Block a user