fix: 생일 표시 버그 수정 및 데이트픽커 개선

- 생일이 생년 이전 년도에 표시되는 버그 수정
- 페이지 진입 애니메이션 추가 (사전 관리, 일정 추가)
- 데이트픽커 12년 단위 이동으로 변경
- 년도 선택 시 월 선택 화면 전환 제거
- 시작 년도 2025년 고정, 이전 이동 비활성화
- PC/모바일 일정 페이지, 관리자 페이지 모두 적용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-19 16:41:34 +09:00
parent 2576a244c0
commit 1a9fa54981
7 changed files with 39 additions and 26 deletions

View file

@ -327,6 +327,11 @@ async function handleMonthlySchedules(db, year, month) {
// 생일 일정 추가 // 생일 일정 추가
for (const member of birthdays) { for (const member of birthdays) {
const birthDate = new Date(member.birth_date); const birthDate = new Date(member.birth_date);
const birthYear = birthDate.getFullYear();
// 조회 연도가 생년보다 이전이면 스킵
if (year < birthYear) continue;
const birthdayThisYear = new Date(year, birthDate.getMonth(), birthDate.getDate()); const birthdayThisYear = new Date(year, birthDate.getMonth(), birthDate.getDate());
const dateKey = birthdayThisYear.toISOString().split('T')[0]; const dateKey = birthdayThisYear.toISOString().split('T')[0];

View file

@ -40,13 +40,15 @@ function CustomDatePicker({ value, onChange, placeholder = '날짜 선택', show
days.push(i); days.push(i);
} }
const startYear = Math.floor(year / 10) * 10 - 1; const MIN_YEAR = 2025;
const startYear = Math.max(MIN_YEAR, Math.floor(year / 12) * 12 - 1);
const years = Array.from({ length: 12 }, (_, i) => startYear + i); const years = Array.from({ length: 12 }, (_, i) => startYear + i);
const canGoPrevYearRange = startYear > MIN_YEAR;
const prevMonth = () => setViewDate(new Date(year, month - 1, 1)); const prevMonth = () => setViewDate(new Date(year, month - 1, 1));
const nextMonth = () => setViewDate(new Date(year, month + 1, 1)); const nextMonth = () => setViewDate(new Date(year, month + 1, 1));
const prevYearRange = () => setViewDate(new Date(year - 10, month, 1)); const prevYearRange = () => canGoPrevYearRange && setViewDate(new Date(Math.max(MIN_YEAR, year - 12), month, 1));
const nextYearRange = () => setViewDate(new Date(year + 10, month, 1)); const nextYearRange = () => setViewDate(new Date(year + 12, month, 1));
const selectDate = (day) => { const selectDate = (day) => {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
@ -57,7 +59,6 @@ function CustomDatePicker({ value, onChange, placeholder = '날짜 선택', show
const selectYear = (y) => { const selectYear = (y) => {
setViewDate(new Date(y, month, 1)); setViewDate(new Date(y, month, 1));
setViewMode('months');
}; };
const selectMonth = (m) => { const selectMonth = (m) => {
@ -124,7 +125,8 @@ function CustomDatePicker({ value, onChange, placeholder = '날짜 선택', show
<button <button
type="button" type="button"
onClick={viewMode === 'years' ? prevYearRange : prevMonth} onClick={viewMode === 'years' ? prevYearRange : prevMonth}
className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors" disabled={viewMode === 'years' && !canGoPrevYearRange}
className={`p-1.5 rounded-lg transition-colors ${viewMode === 'years' && !canGoPrevYearRange ? 'opacity-30' : 'hover:bg-gray-100'}`}
> >
<ChevronLeft size={20} className="text-gray-600" /> <ChevronLeft size={20} className="text-gray-600" />
</button> </button>

View file

@ -1239,8 +1239,10 @@ function CalendarPicker({
} }
}; };
const [yearRangeStart, setYearRangeStart] = useState(Math.floor(year / 12) * 12); const MIN_YEAR = 2025;
const [yearRangeStart, setYearRangeStart] = useState(MIN_YEAR);
const yearRange = Array.from({ length: 12 }, (_, i) => yearRangeStart + i); const yearRange = Array.from({ length: 12 }, (_, i) => yearRangeStart + i);
const canGoPrevYearRange = yearRangeStart > MIN_YEAR;
// //
useEffect(() => { useEffect(() => {
@ -1364,16 +1366,17 @@ function CalendarPicker({
> >
{/* 년도 범위 헤더 */} {/* 년도 범위 헤더 */}
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<button <button
onClick={() => setYearRangeStart(yearRangeStart - 12)} onClick={() => canGoPrevYearRange && setYearRangeStart(Math.max(MIN_YEAR, yearRangeStart - 12))}
className="p-1" disabled={!canGoPrevYearRange}
className={`p-1 ${canGoPrevYearRange ? '' : 'opacity-30'}`}
> >
<ChevronLeft size={18} /> <ChevronLeft size={18} />
</button> </button>
<span className="font-semibold text-sm"> <span className="font-semibold text-sm">
{yearRangeStart} - {yearRangeStart + 11} {yearRangeStart} - {yearRangeStart + 11}
</span> </span>
<button <button
onClick={() => setYearRangeStart(yearRangeStart + 12)} onClick={() => setYearRangeStart(yearRangeStart + 12)}
className="p-1" className="p-1"
> >

View file

@ -273,9 +273,11 @@ function AdminSchedule() {
const days = ['일', '월', '화', '수', '목', '금', '토']; const days = ['일', '월', '화', '수', '목', '금', '토'];
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']; const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'];
// ( 10 - Schedule.jsx ) // (2025 , 12 )
const startYear = Math.floor(year / 10) * 10 - 1; const MIN_YEAR = 2025;
const startYear = Math.max(MIN_YEAR, Math.floor(year / 12) * 12 - 1);
const yearRange = Array.from({ length: 12 }, (_, i) => startYear + i); const yearRange = Array.from({ length: 12 }, (_, i) => startYear + i);
const canGoPrevYearRange = startYear > MIN_YEAR;
// / // /
const isCurrentYear = (y) => new Date().getFullYear() === y; const isCurrentYear = (y) => new Date().getFullYear() === y;
@ -482,14 +484,13 @@ function AdminSchedule() {
setSchedules([]); // setSchedules([]); //
}; };
// // (12 , 2025 )
const prevYearRange = () => setCurrentDate(new Date(year - 10, month, 1)); const prevYearRange = () => canGoPrevYearRange && setCurrentDate(new Date(Math.max(MIN_YEAR, year - 12), month, 1));
const nextYearRange = () => setCurrentDate(new Date(year + 10, month, 1)); const nextYearRange = () => setCurrentDate(new Date(year + 12, month, 1));
// //
const selectYear = (newYear) => { const selectYear = (newYear) => {
setCurrentDate(new Date(newYear, month, 1)); setCurrentDate(new Date(newYear, month, 1));
setViewMode('months');
}; };
// //
@ -730,7 +731,8 @@ function AdminSchedule() {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<button <button
onClick={prevYearRange} onClick={prevYearRange}
className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors" disabled={!canGoPrevYearRange}
className={`p-1.5 rounded-lg transition-colors ${canGoPrevYearRange ? 'hover:bg-gray-100' : 'opacity-30'}`}
> >
<ChevronLeft size={20} className="text-gray-600" /> <ChevronLeft size={20} className="text-gray-600" />
</button> </button>

View file

@ -613,7 +613,7 @@ function AdminScheduleDict() {
) : ( ) : (
<div className="overflow-x-auto max-h-[500px] overflow-y-auto"> <div className="overflow-x-auto max-h-[500px] overflow-y-auto">
<table className="w-full"> <table className="w-full">
<thead className="bg-gray-50 sticky top-0"> <thead className="bg-gray-50 sticky top-0 z-30">
<tr> <tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-16">#</th> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-16">#</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">단어</th> <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">단어</th>

View file

@ -34,7 +34,7 @@ const formVariants = {
visible: { visible: {
opacity: 1, opacity: 1,
y: 0, y: 0,
transition: { duration: 0.3, ease: "easeOut" }, transition: { duration: 0.3, ease: "easeOut", delay: 0.3 },
}, },
exit: { exit: {
opacity: 0, opacity: 0,

View file

@ -431,7 +431,6 @@ function Schedule() {
const selectYear = (newYear) => { const selectYear = (newYear) => {
setCurrentDate(new Date(newYear, month, 1)); setCurrentDate(new Date(newYear, month, 1));
setViewMode('months');
}; };
const selectMonth = (newMonth) => { const selectMonth = (newMonth) => {
@ -568,12 +567,14 @@ function Schedule() {
return year === now.getFullYear() && m === now.getMonth(); return year === now.getFullYear() && m === now.getMonth();
}; };
// // (2025 )
const [yearRangeStart, setYearRangeStart] = useState(currentYear - 1); const MIN_YEAR = 2025;
const [yearRangeStart, setYearRangeStart] = useState(MIN_YEAR);
const yearRange = Array.from({ length: 12 }, (_, i) => yearRangeStart + i); const yearRange = Array.from({ length: 12 }, (_, i) => yearRangeStart + i);
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']; const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'];
const prevYearRange = () => setYearRangeStart(prev => prev - 3); const canGoPrevYearRange = yearRangeStart > MIN_YEAR;
const nextYearRange = () => setYearRangeStart(prev => prev + 3); const prevYearRange = () => canGoPrevYearRange && setYearRangeStart(prev => Math.max(MIN_YEAR, prev - 12));
const nextYearRange = () => setYearRangeStart(prev => prev + 12);
// //
const getSelectedCategoryNames = () => { const getSelectedCategoryNames = () => {
@ -680,7 +681,7 @@ function Schedule() {
className="absolute top-20 left-8 right-8 mx-auto w-80 bg-white rounded-xl shadow-lg border border-gray-200 p-4 z-10" className="absolute top-20 left-8 right-8 mx-auto w-80 bg-white rounded-xl shadow-lg border border-gray-200 p-4 z-10"
> >
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<button onClick={prevYearRange} className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors"> <button onClick={prevYearRange} disabled={!canGoPrevYearRange} className={`p-1.5 rounded-lg transition-colors ${canGoPrevYearRange ? 'hover:bg-gray-100' : 'opacity-30'}`}>
<ChevronLeft size={20} className="text-gray-600" /> <ChevronLeft size={20} className="text-gray-600" />
</button> </button>
<span className="font-medium text-gray-900"> <span className="font-medium text-gray-900">