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) {
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 dateKey = birthdayThisYear.toISOString().split('T')[0];

View file

@ -40,13 +40,15 @@ function CustomDatePicker({ value, onChange, placeholder = '날짜 선택', show
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 canGoPrevYearRange = startYear > MIN_YEAR;
const prevMonth = () => 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 nextYearRange = () => 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 + 12, month, 1));
const selectDate = (day) => {
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) => {
setViewDate(new Date(y, month, 1));
setViewMode('months');
};
const selectMonth = (m) => {
@ -124,7 +125,8 @@ function CustomDatePicker({ value, onChange, placeholder = '날짜 선택', show
<button
type="button"
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" />
</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 canGoPrevYearRange = yearRangeStart > MIN_YEAR;
//
useEffect(() => {
@ -1364,16 +1366,17 @@ function CalendarPicker({
>
{/* 년도 범위 헤더 */}
<div className="flex items-center justify-between mb-3">
<button
onClick={() => setYearRangeStart(yearRangeStart - 12)}
className="p-1"
<button
onClick={() => canGoPrevYearRange && setYearRangeStart(Math.max(MIN_YEAR, yearRangeStart - 12))}
disabled={!canGoPrevYearRange}
className={`p-1 ${canGoPrevYearRange ? '' : 'opacity-30'}`}
>
<ChevronLeft size={18} />
</button>
<span className="font-semibold text-sm">
{yearRangeStart} - {yearRangeStart + 11}
</span>
<button
<button
onClick={() => setYearRangeStart(yearRangeStart + 12)}
className="p-1"
>

View file

@ -273,9 +273,11 @@ function AdminSchedule() {
const days = ['일', '월', '화', '수', '목', '금', '토'];
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'];
// ( 10 - Schedule.jsx )
const startYear = Math.floor(year / 10) * 10 - 1;
// (2025 , 12 )
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 canGoPrevYearRange = startYear > MIN_YEAR;
// /
const isCurrentYear = (y) => new Date().getFullYear() === y;
@ -482,14 +484,13 @@ function AdminSchedule() {
setSchedules([]); //
};
//
const prevYearRange = () => setCurrentDate(new Date(year - 10, month, 1));
const nextYearRange = () => setCurrentDate(new Date(year + 10, month, 1));
// (12 , 2025 )
const prevYearRange = () => canGoPrevYearRange && setCurrentDate(new Date(Math.max(MIN_YEAR, year - 12), month, 1));
const nextYearRange = () => setCurrentDate(new Date(year + 12, month, 1));
//
//
const selectYear = (newYear) => {
setCurrentDate(new Date(newYear, month, 1));
setViewMode('months');
};
//
@ -730,7 +731,8 @@ function AdminSchedule() {
<div className="flex items-center justify-between mb-4">
<button
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" />
</button>

View file

@ -613,7 +613,7 @@ function AdminScheduleDict() {
) : (
<div className="overflow-x-auto max-h-[500px] overflow-y-auto">
<table className="w-full">
<thead className="bg-gray-50 sticky top-0">
<thead className="bg-gray-50 sticky top-0 z-30">
<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">단어</th>

View file

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

View file

@ -431,7 +431,6 @@ function Schedule() {
const selectYear = (newYear) => {
setCurrentDate(new Date(newYear, month, 1));
setViewMode('months');
};
const selectMonth = (newMonth) => {
@ -568,12 +567,14 @@ function Schedule() {
return year === now.getFullYear() && m === now.getMonth();
};
//
const [yearRangeStart, setYearRangeStart] = useState(currentYear - 1);
// (2025 )
const MIN_YEAR = 2025;
const [yearRangeStart, setYearRangeStart] = useState(MIN_YEAR);
const yearRange = Array.from({ length: 12 }, (_, i) => yearRangeStart + i);
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'];
const prevYearRange = () => setYearRangeStart(prev => prev - 3);
const nextYearRange = () => setYearRangeStart(prev => prev + 3);
const canGoPrevYearRange = yearRangeStart > MIN_YEAR;
const prevYearRange = () => canGoPrevYearRange && setYearRangeStart(prev => Math.max(MIN_YEAR, prev - 12));
const nextYearRange = () => setYearRangeStart(prev => prev + 12);
//
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"
>
<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" />
</button>
<span className="font-medium text-gray-900">