"use client"; import React, { useEffect, useRef, useState } from "react"; /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ interface DateRangePickerProps { from: string; // YYYY-MM-DD to: string; // YYYY-MM-DD onChange: (from: string, to: string) => void; } /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ function daysInMonth(year: number, month: number): number { return new Date(year, month + 1, 0).getDate(); } function firstDayOfMonth(year: number, month: number): number { return new Date(year, month, 1).getDay(); // 0=Sun } function fmt(d: Date): string { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } function fmtDisplay(dateStr: string): string { if (!dateStr) return ""; const [y, m, d] = dateStr.split("-"); return `${y}.${m}.${d}`; } function isSame(a: string, b: string): boolean { return a === b; } function isBetween(date: string, from: string, to: string): boolean { return date >= from && date <= to; } const WEEKDAYS = ["일", "월", "화", "수", "목", "금", "토"]; const MONTH_NAMES = [ "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", ]; /* ------------------------------------------------------------------ */ /* Component */ /* ------------------------------------------------------------------ */ export function DateRangePicker({ from, to, onChange }: DateRangePickerProps) { const [open, setOpen] = useState(false); const [selecting, setSelecting] = useState<"from" | "to" | null>(null); const [tempFrom, setTempFrom] = useState(from); const [tempTo, setTempTo] = useState(to); const [viewYear, setViewYear] = useState(() => new Date().getFullYear()); const [viewMonth, setViewMonth] = useState(() => new Date().getMonth()); const containerRef = useRef(null); // Close on outside click useEffect(() => { function handleClick(e: MouseEvent) { if ( containerRef.current && !containerRef.current.contains(e.target as Node) ) { setOpen(false); } } if (open) document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, [open]); const handleOpen = () => { setTempFrom(from); setTempTo(to); setSelecting("from"); const d = from ? new Date(from) : new Date(); setViewYear(d.getFullYear()); setViewMonth(d.getMonth()); setOpen(true); }; const handleDayClick = (dateStr: string) => { if (selecting === "from") { setTempFrom(dateStr); setTempTo(dateStr); // 같은 날짜 = 당일 setSelecting("to"); } else { // to 선택 if (dateStr < tempFrom) { // 시작일보다 이전 선택 → 시작일로 교체 setTempFrom(dateStr); setTempTo(dateStr); setSelecting("to"); } else { setTempTo(dateStr); onChange(tempFrom, dateStr); setOpen(false); setSelecting(null); } } }; const prevMonth = () => { if (viewMonth === 0) { setViewYear(viewYear - 1); setViewMonth(11); } else setViewMonth(viewMonth - 1); }; const nextMonth = () => { if (viewMonth === 11) { setViewYear(viewYear + 1); setViewMonth(0); } else setViewMonth(viewMonth + 1); }; // Quick select presets const today = fmt(new Date()); const presets = [ { label: "오늘", from: today, to: today }, { label: "이번주", from: fmt( new Date( new Date().setDate(new Date().getDate() - new Date().getDay()), ), ), to: today, }, { label: "이번달", from: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}-01`, to: today, }, ]; // Display text const displayText = from && to ? isSame(from, to) ? fmtDisplay(from) : `${fmtDisplay(from)} ~ ${fmtDisplay(to)}` : "기간 선택"; // Build calendar grid const totalDays = daysInMonth(viewYear, viewMonth); const startDay = firstDayOfMonth(viewYear, viewMonth); const cells: (string | null)[] = []; for (let i = 0; i < startDay; i++) cells.push(null); for (let d = 1; d <= totalDays; d++) { const dateStr = `${viewYear}-${String(viewMonth + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`; cells.push(dateStr); } return (
{/* Trigger Button */}
{/* Calendar Popup */} {open && (
{/* Header hint */}

{selecting === "from" ? "시작일을 선택하세요" : "종료일을 선택하세요 (같은 날 = 당일)"}

{/* Quick Presets */}
{presets.map((p) => ( ))}
{/* Month Navigation */}
{viewYear}년 {MONTH_NAMES[viewMonth]}
{/* Weekday Headers */}
{WEEKDAYS.map((d, i) => (
{d}
))}
{/* Day Grid */}
{cells.map((dateStr, idx) => { if (!dateStr) return
; const day = parseInt(dateStr.split("-")[2], 10); const dayOfWeek = new Date(dateStr).getDay(); const isStart = isSame(dateStr, tempFrom); const isEnd = isSame(dateStr, tempTo); const isInRange = tempFrom && tempTo && isBetween(dateStr, tempFrom, tempTo); const isToday = isSame(dateStr, today); let bgClass = "hover:bg-gray-100"; let textClass = dayOfWeek === 0 ? "text-red-500" : dayOfWeek === 6 ? "text-blue-500" : "text-gray-700"; if (isStart || isEnd) { bgClass = "bg-cyan-600 text-white"; textClass = "text-white"; } else if (isInRange) { bgClass = "bg-cyan-50"; textClass = "text-cyan-700"; } return ( ); })}
{/* Selected Range Display */} {tempFrom && (
{isSame(tempFrom, tempTo) ? fmtDisplay(tempFrom) : `${fmtDisplay(tempFrom)} ~ ${fmtDisplay(tempTo)}`}
)}
)}
); }