refactor(mobile): Swiper 제거하고 터치 제스처 방식 CalendarPicker로 변경
This commit is contained in:
parent
0750dded97
commit
767cbcaf5f
4 changed files with 234 additions and 49 deletions
92
frontend/package-lock.json
generated
92
frontend/package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
||||||
"framer-motion": "^11.0.8",
|
"framer-motion": "^11.0.8",
|
||||||
"lucide-react": "^0.344.0",
|
"lucide-react": "^0.344.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-calendar": "^6.0.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
@ -1259,6 +1260,15 @@
|
||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@wojtekmaj/date-utils": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Do66mSlSNifFFuo3l9gNKfRMSFi26CRuQMsDJuuKO/ekrDWuTTtE4ZQxoFCUOG+NgxnpSeBq/k5TY8ZseEzLpA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/wojtekmaj/date-utils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/any-promise": {
|
"node_modules/any-promise": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
|
|
@ -1463,6 +1473,15 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||||
|
|
@ -1717,6 +1736,18 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-user-locale": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-iJfHSmdYV39UUBw7Jq6GJzeJxUr4U+S03qdhVuDsR9gCEnfbqLy9gYDJFBJQL1riqolFUKQvx36mEkp2iGgJ3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"memoize": "^10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
|
|
@ -1898,6 +1929,21 @@
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/memoize": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize/-/memoize-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-function": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sindresorhus/memoize?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge2": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
|
|
@ -1922,6 +1968,18 @@
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mimic-function": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/motion-dom": {
|
"node_modules/motion-dom": {
|
||||||
"version": "11.18.1",
|
"version": "11.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
|
||||||
|
|
@ -2265,6 +2323,31 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-calendar": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-6wqaki3Us0DNDjZDr0DYIzhSFprNoy4FdPT9Pjy5aD2hJJVjtJwmdMT9VmrTUo949nlk35BOxehThxX62RkuRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@wojtekmaj/date-utils": "^2.0.2",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"get-user-locale": "^3.0.0",
|
||||||
|
"warning": "^4.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/wojtekmaj/react-calendar?sponsor=1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-colorful": {
|
"node_modules/react-colorful": {
|
||||||
"version": "5.6.1",
|
"version": "5.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
|
||||||
|
|
@ -2906,6 +2989,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/warning": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"framer-motion": "^11.0.8",
|
"framer-motion": "^11.0.8",
|
||||||
"lucide-react": "^0.344.0",
|
"lucide-react": "^0.344.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-calendar": "^6.0.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
|
||||||
|
|
@ -98,3 +98,105 @@ body {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Swiper autoHeight 지원 */
|
||||||
|
.swiper-slide {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__navigation button:hover {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__navigation__label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__month-view__weekdays {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__month-view__weekdays__weekday {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__month-view__weekdays__weekday abbr {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 일요일 (빨간색) */
|
||||||
|
.mobile-calendar-wrapper
|
||||||
|
.react-calendar__month-view__weekdays__weekday:first-child {
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 토요일 (파란색) */
|
||||||
|
.mobile-calendar-wrapper
|
||||||
|
.react-calendar__month-view__weekdays__weekday:last-child {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile:hover {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile abbr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 이웃 달 날짜 (흐리게) */
|
||||||
|
.mobile-calendar-wrapper
|
||||||
|
.react-calendar__month-view__days__day--neighboringMonth {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 일요일 */
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile.sunday abbr {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 토요일 */
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile.saturday abbr {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 오늘 */
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile--now abbr {
|
||||||
|
background-color: #548360;
|
||||||
|
color: white;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 선택된 날짜 */
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile--active abbr {
|
||||||
|
background-color: #548360;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile--active:enabled:hover abbr,
|
||||||
|
.mobile-calendar-wrapper .react-calendar__tile--active:enabled:focus abbr {
|
||||||
|
background-color: #456e50;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { Clock, Tag, Link2, ChevronLeft, ChevronRight, ChevronDown, Search, X, Calendar } from 'lucide-react';
|
import { Clock, Tag, Link2, ChevronLeft, ChevronRight, ChevronDown, Search, X, Calendar } from 'lucide-react';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useInView } from 'react-intersection-observer';
|
import { useInView } from 'react-intersection-observer';
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
|
||||||
import 'swiper/css';
|
|
||||||
|
|
||||||
// 모바일 일정 페이지
|
// 모바일 일정 페이지
|
||||||
function MobileSchedule() {
|
function MobileSchedule() {
|
||||||
|
|
@ -399,7 +397,10 @@ function TimelineScheduleCard({ schedule, categoryColor, categories, delay = 0 }
|
||||||
// 달력 선택기 컴포넌트
|
// 달력 선택기 컴포넌트
|
||||||
function CalendarPicker({ selectedDate, schedules = [], categories = [], onSelectDate }) {
|
function CalendarPicker({ selectedDate, schedules = [], categories = [], onSelectDate }) {
|
||||||
const [viewDate, setViewDate] = useState(new Date(selectedDate));
|
const [viewDate, setViewDate] = useState(new Date(selectedDate));
|
||||||
const swiperRef = useRef(null);
|
|
||||||
|
// 터치 스와이프 핸들링
|
||||||
|
const touchStartX = useRef(0);
|
||||||
|
const touchEndX = useRef(0);
|
||||||
|
|
||||||
// 날짜별 일정 존재 여부 및 카테고리 색상
|
// 날짜별 일정 존재 여부 및 카테고리 색상
|
||||||
const scheduleDates = useMemo(() => {
|
const scheduleDates = useMemo(() => {
|
||||||
|
|
@ -490,33 +491,42 @@ function CalendarPicker({ selectedDate, schedules = [], categories = [], onSelec
|
||||||
return () => { document.body.style.overflow = ''; };
|
return () => { document.body.style.overflow = ''; };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 슬라이드에 표시할 3개월 데이터 (이전, 현재, 다음)
|
// 현재 달 캘린더 데이터
|
||||||
const slides = useMemo(() => {
|
const currentMonthDays = useMemo(() => {
|
||||||
return [-1, 0, 1].map(offset => {
|
return getCalendarDays(year, month);
|
||||||
const d = new Date(year, month + offset, 1);
|
|
||||||
return {
|
|
||||||
year: d.getFullYear(),
|
|
||||||
month: d.getMonth(),
|
|
||||||
days: getCalendarDays(d.getFullYear(), d.getMonth())
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [year, month, getCalendarDays]);
|
}, [year, month, getCalendarDays]);
|
||||||
|
|
||||||
// 스와이프 핸들러
|
// 터치 핸들러
|
||||||
const handleSlideChange = (swiper) => {
|
const handleTouchStart = (e) => {
|
||||||
const direction = swiper.activeIndex - 1; // -1, 0, 1
|
touchStartX.current = e.touches[0].clientX;
|
||||||
if (direction !== 0) {
|
};
|
||||||
changeMonth(direction);
|
|
||||||
// 다시 중앙으로 리셋 (무한 스와이프를 위해)
|
const handleTouchMove = (e) => {
|
||||||
setTimeout(() => {
|
touchEndX.current = e.touches[0].clientX;
|
||||||
swiper.slideTo(1, 0);
|
};
|
||||||
}, 0);
|
|
||||||
|
const handleTouchEnd = () => {
|
||||||
|
const diff = touchStartX.current - touchEndX.current;
|
||||||
|
const threshold = 50;
|
||||||
|
|
||||||
|
if (Math.abs(diff) > threshold) {
|
||||||
|
if (diff > 0) {
|
||||||
|
changeMonth(1);
|
||||||
|
} else {
|
||||||
|
changeMonth(-1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
touchStartX.current = 0;
|
||||||
|
touchEndX.current = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 월 렌더링 컴포넌트
|
// 월 렌더링 컴포넌트
|
||||||
const renderMonth = (slideData) => (
|
const renderMonth = (days) => (
|
||||||
<div>
|
<div
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
onTouchEnd={handleTouchEnd}
|
||||||
|
>
|
||||||
{/* 요일 헤더 */}
|
{/* 요일 헤더 */}
|
||||||
<div className="grid grid-cols-7 gap-1 mb-2">
|
<div className="grid grid-cols-7 gap-1 mb-2">
|
||||||
{['일', '월', '화', '수', '목', '금', '토'].map((day, i) => (
|
{['일', '월', '화', '수', '목', '금', '토'].map((day, i) => (
|
||||||
|
|
@ -533,7 +543,7 @@ function CalendarPicker({ selectedDate, schedules = [], categories = [], onSelec
|
||||||
|
|
||||||
{/* 날짜 그리드 */}
|
{/* 날짜 그리드 */}
|
||||||
<div className="grid grid-cols-7 gap-1">
|
<div className="grid grid-cols-7 gap-1">
|
||||||
{slideData.days.map((item, index) => {
|
{days.map((item, index) => {
|
||||||
const dayOfWeek = index % 7;
|
const dayOfWeek = index % 7;
|
||||||
const isSunday = dayOfWeek === 0;
|
const isSunday = dayOfWeek === 0;
|
||||||
const isSaturday = dayOfWeek === 6;
|
const isSaturday = dayOfWeek === 6;
|
||||||
|
|
@ -671,9 +681,7 @@ function CalendarPicker({ selectedDate, schedules = [], categories = [], onSelec
|
||||||
{/* 달력 헤더 */}
|
{/* 달력 헤더 */}
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => changeMonth(-1)}
|
||||||
swiperRef.current?.slidePrev();
|
|
||||||
}}
|
|
||||||
className="p-1"
|
className="p-1"
|
||||||
>
|
>
|
||||||
<ChevronLeft size={18} />
|
<ChevronLeft size={18} />
|
||||||
|
|
@ -686,33 +694,15 @@ function CalendarPicker({ selectedDate, schedules = [], categories = [], onSelec
|
||||||
<ChevronDown size={16} />
|
<ChevronDown size={16} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => changeMonth(1)}
|
||||||
swiperRef.current?.slideNext();
|
|
||||||
}}
|
|
||||||
className="p-1"
|
className="p-1"
|
||||||
>
|
>
|
||||||
<ChevronRight size={18} />
|
<ChevronRight size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Swiper 달력 */}
|
{/* 달력 (터치 스와이프 지원) */}
|
||||||
<Swiper
|
{renderMonth(currentMonthDays)}
|
||||||
onSwiper={(swiper) => { swiperRef.current = swiper; }}
|
|
||||||
initialSlide={1}
|
|
||||||
onSlideChangeTransitionEnd={handleSlideChange}
|
|
||||||
spaceBetween={20}
|
|
||||||
slidesPerView={1}
|
|
||||||
speed={200}
|
|
||||||
touchRatio={1}
|
|
||||||
resistance={true}
|
|
||||||
resistanceRatio={0.5}
|
|
||||||
>
|
|
||||||
{slides.map((slide, idx) => (
|
|
||||||
<SwiperSlide key={`${slide.year}-${slide.month}-${idx}`}>
|
|
||||||
{renderMonth(slide)}
|
|
||||||
</SwiperSlide>
|
|
||||||
))}
|
|
||||||
</Swiper>
|
|
||||||
|
|
||||||
{/* 오늘 버튼 */}
|
{/* 오늘 버튼 */}
|
||||||
<div className="mt-3 flex justify-center">
|
<div className="mt-3 flex justify-center">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue