From 9d1f54c68a4eef12d93be51aed22f70a110fcffd Mon Sep 17 00:00:00 2001 From: caadiq Date: Thu, 22 Jan 2026 12:08:47 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PC 컴포넌트를 components/pc/로 이동 (Calendar, ScheduleCard, BirthdayCard, CategoryFilter) - Mobile 컴포넌트를 components/mobile/로 이동 (Mobile 접두사 제거) - components/schedule/에는 공용 코드만 유지 (confetti.js, AdminScheduleCard) - Schedule, Home 페이지의 import 경로 업데이트 - 관련 문서 업데이트 Co-Authored-By: Claude Opus 4.5 --- docs/frontend-refactoring.md | 54 ++++- docs/migration.md | 134 +++++++----- .../src/components/mobile/BirthdayCard.jsx | 84 ++++++++ .../Calendar.jsx} | 6 +- .../ScheduleCard.jsx} | 6 +- .../ScheduleListCard.jsx} | 6 +- .../ScheduleSearchCard.jsx} | 6 +- frontend-temp/src/components/mobile/index.js | 8 + .../src/components/pc/BirthdayCard.jsx | 60 ++++++ .../components/{schedule => pc}/Calendar.jsx | 2 +- .../{schedule => pc}/CategoryFilter.jsx | 2 +- .../{schedule => pc}/ScheduleCard.jsx | 2 +- frontend-temp/src/components/pc/index.js | 7 + .../src/components/schedule/BirthdayCard.jsx | 200 ------------------ .../src/components/schedule/confetti.js | 60 ++++++ .../src/components/schedule/index.js | 17 +- frontend-temp/src/pages/home/mobile/Home.jsx | 2 +- frontend-temp/src/pages/home/pc/Home.jsx | 2 +- .../src/pages/schedule/mobile/Schedule.jsx | 12 +- .../src/pages/schedule/pc/Schedule.jsx | 4 +- 20 files changed, 373 insertions(+), 301 deletions(-) create mode 100644 frontend-temp/src/components/mobile/BirthdayCard.jsx rename frontend-temp/src/components/{schedule/MobileCalendar.jsx => mobile/Calendar.jsx} (99%) rename frontend-temp/src/components/{schedule/MobileScheduleCard.jsx => mobile/ScheduleCard.jsx} (95%) rename frontend-temp/src/components/{schedule/MobileScheduleListCard.jsx => mobile/ScheduleListCard.jsx} (95%) rename frontend-temp/src/components/{schedule/MobileScheduleSearchCard.jsx => mobile/ScheduleSearchCard.jsx} (97%) create mode 100644 frontend-temp/src/components/pc/BirthdayCard.jsx rename frontend-temp/src/components/{schedule => pc}/Calendar.jsx (99%) rename frontend-temp/src/components/{schedule => pc}/CategoryFilter.jsx (98%) rename frontend-temp/src/components/{schedule => pc}/ScheduleCard.jsx (98%) delete mode 100644 frontend-temp/src/components/schedule/BirthdayCard.jsx create mode 100644 frontend-temp/src/components/schedule/confetti.js diff --git a/docs/frontend-refactoring.md b/docs/frontend-refactoring.md index 8e3c9e0..dcb8afe 100644 --- a/docs/frontend-refactoring.md +++ b/docs/frontend-refactoring.md @@ -1547,18 +1547,54 @@ rm -rf frontend-backup | Phase | 작업 | 상태 | |-------|------|------| -| 1 | 프로젝트 기반 설정 | ⬜ 대기 | -| 2 | 유틸리티 및 상수 | ⬜ 대기 | -| 3 | Stores 구축 | ⬜ 대기 | -| 4 | API 계층 | ⬜ 대기 | -| 5 | 커스텀 훅 | ⬜ 대기 | -| 6 | 공통 컴포넌트 | ⬜ 대기 | -| 7 | Schedule 페이지 | ⬜ 대기 | -| 8 | Album 페이지 | ⬜ 대기 | -| 9 | 기타 Public 페이지 | ⬜ 대기 | +| 1 | 프로젝트 기반 설정 | ✅ 완료 | +| 2 | 유틸리티 및 상수 | ✅ 완료 | +| 3 | Stores 구축 | ✅ 완료 | +| 4 | API 계층 (공개 API) | ✅ 완료 | +| 5 | 커스텀 훅 | ✅ 완료 | +| 6 | 공통 컴포넌트 | ✅ 완료 | +| 7 | Schedule 페이지 | ✅ 완료 | +| 8 | Album 페이지 | ✅ 완료 | +| 9 | 기타 Public 페이지 (Home, Members) | ✅ 완료 | +| 9-1 | NotFound 페이지 | ⬜ 대기 | | 10 | Admin 페이지 | ⬜ 대기 | | 11 | 최종 검증 및 교체 | ⬜ 대기 | +### 세부 완료 현황 + +**레이아웃 컴포넌트** +- ✅ PC: Layout, Header, Footer +- ✅ Mobile: Layout + +**공개 페이지** +- ✅ Home (PC/Mobile) +- ✅ Members (PC/Mobile) +- ✅ Album, AlbumDetail, AlbumGallery, TrackDetail (PC/Mobile) +- ✅ Schedule, ScheduleDetail, Birthday (PC/Mobile) +- ⬜ NotFound (PC/Mobile) + +**일정 컴포넌트** +- ✅ Calendar, MobileCalendar +- ✅ ScheduleCard, MobileScheduleCard, MobileScheduleListCard, MobileScheduleSearchCard +- ✅ BirthdayCard, CategoryFilter, AdminScheduleCard + +**공통 컴포넌트** +- ✅ Loading, ErrorBoundary, Toast, Lightbox +- ✅ LightboxIndicator, Tooltip, ScrollToTop + +**훅** +- ✅ useAlbumData, useMemberData, useScheduleData, useScheduleSearch +- ✅ useScheduleFiltering, useCalendar, useMediaQuery, useAdminAuth +- ⬜ useToast + +**스토어** +- ✅ useAuthStore, useScheduleStore, useUIStore + +**관리자 (별도 요청 시 진행)** +- ⬜ 관리자 API 전체 +- ⬜ 관리자 컴포넌트 전체 +- ⬜ 관리자 페이지 전체 + --- ## 7. 참고 사항 diff --git a/docs/migration.md b/docs/migration.md index 7a6d089..4dd36de 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -233,35 +233,56 @@ function App() { - [x] useMemberData.js - [x] useScheduleData.js - [x] useScheduleSearch.js +- [x] useScheduleFiltering.js - [x] useCalendar.js +- [x] useMediaQuery.js - [x] useAdminAuth.js -- [ ] useToast.js +- [ ] useToast.js (Toast 컴포넌트는 있으나 훅은 미구현) ### 스토어 (stores/) - [x] useScheduleStore.js - [x] useAuthStore.js +- [x] useUIStore.js + +### 상수 (constants/) +- [x] index.js (CATEGORY_ID, MIN_YEAR, SEARCH_LIMIT 등) ### 유틸리티 (utils/) +- [x] index.js - [x] date.js - [x] format.js +- [x] cn.js (className 유틸) +- [x] schedule.js (일정 관련 유틸) ### 공통 컴포넌트 (components/common/) - [x] Loading.jsx - [x] ErrorBoundary.jsx - [x] Toast.jsx - [x] Lightbox.jsx -- [ ] LightboxIndicator.jsx -- [ ] Tooltip.jsx -- [ ] ScrollToTop.jsx +- [x] LightboxIndicator.jsx +- [x] Tooltip.jsx +- [x] ScrollToTop.jsx -### PC 레이아웃 컴포넌트 (components/pc/) -- [ ] Layout.jsx (Outlet 사용) -- [ ] Header.jsx -- [ ] Footer.jsx +### PC 컴포넌트 (components/pc/) +- [x] Layout.jsx +- [x] Header.jsx +- [x] Footer.jsx +- [x] Calendar.jsx +- [x] ScheduleCard.jsx +- [x] BirthdayCard.jsx +- [x] CategoryFilter.jsx -### Mobile 레이아웃 컴포넌트 (components/mobile/) -- [ ] Layout.jsx (Outlet 사용) -- [ ] MobileNav.jsx +### Mobile 컴포넌트 (components/mobile/) +- [x] Layout.jsx +- [x] Calendar.jsx +- [x] ScheduleCard.jsx +- [x] ScheduleListCard.jsx +- [x] ScheduleSearchCard.jsx +- [x] BirthdayCard.jsx + +### 공용 일정 컴포넌트 (components/schedule/) +- [x] confetti.js (fireBirthdayConfetti) +- [x] AdminScheduleCard.jsx ### 관리자 컴포넌트 (components/admin/) - [ ] AdminLayout.jsx @@ -272,32 +293,35 @@ function App() { - [ ] NumberPicker.jsx ### 페이지 - Home (pages/home/) -- [ ] pc/Home.jsx -- [ ] mobile/Home.jsx +- [x] pc/Home.jsx +- [x] mobile/Home.jsx ### 페이지 - Members (pages/members/) -- [ ] pc/Members.jsx -- [ ] mobile/Members.jsx +- [x] pc/Members.jsx +- [x] mobile/Members.jsx ### 페이지 - Album (pages/album/) -- [ ] pc/Album.jsx -- [ ] pc/AlbumDetail.jsx -- [ ] pc/AlbumGallery.jsx -- [ ] pc/TrackDetail.jsx -- [ ] mobile/Album.jsx -- [ ] mobile/AlbumDetail.jsx -- [ ] mobile/AlbumGallery.jsx -- [ ] mobile/TrackDetail.jsx +- [x] pc/Album.jsx +- [x] pc/AlbumDetail.jsx +- [x] pc/AlbumGallery.jsx +- [x] pc/TrackDetail.jsx +- [x] mobile/Album.jsx +- [x] mobile/AlbumDetail.jsx +- [x] mobile/AlbumGallery.jsx +- [x] mobile/TrackDetail.jsx ### 페이지 - Schedule (pages/schedule/) -- [ ] sections/DefaultSection.jsx -- [ ] sections/XSection.jsx -- [ ] sections/YoutubeSection.jsx -- [ ] pc/Schedule.jsx -- [ ] pc/ScheduleDetail.jsx -- [ ] pc/Birthday.jsx -- [ ] mobile/Schedule.jsx -- [ ] mobile/ScheduleDetail.jsx +- [x] sections/DefaultSection.jsx +- [x] sections/XSection.jsx +- [x] sections/YoutubeSection.jsx +- [x] sections/utils.js +- [x] sections/index.js +- [x] pc/Schedule.jsx +- [x] pc/ScheduleDetail.jsx +- [x] pc/Birthday.jsx +- [x] mobile/Schedule.jsx +- [x] mobile/ScheduleDetail.jsx +- [x] mobile/Birthday.jsx ### 페이지 - Common (pages/common/) - [ ] pc/NotFound.jsx @@ -321,18 +345,18 @@ function App() { - [ ] dict/Manager.jsx ### 기타 -- [ ] App.jsx (BrowserView/MobileView 라우팅) -- [ ] main.jsx +- [x] App.jsx (BrowserView/MobileView 라우팅) +- [x] main.jsx ### CSS 파일 - [x] index.css -- [ ] mobile.css (모바일 전용 스타일, 달력 등) -- [ ] pc.css (PC 전용 스타일) +- [x] mobile.css (모바일 전용 스타일, 달력 등) +- [x] pc.css (PC 전용 스타일) ### 기타 파일 -- [ ] data/dummy.js (개발용 더미 데이터) -- [ ] .env (VITE_KAKAO_JS_KEY - 콘서트 장소 검색용, 미구현) -- [ ] public/favicon.ico +- [ ] data/dummy.js (개발용 더미 데이터, 필요 시 추가) +- [ ] .env (VITE_KAKAO_JS_KEY - 콘서트 장소 검색용, 관리자 기능에서 사용) +- [ ] public/favicon.ico (필요 시 추가) ## 사용 라이브러리 (package.json) @@ -396,27 +420,29 @@ import 'swiper/css'; ## 마이그레이션 진행 상황 -### 완료된 작업 (재검토 필요) +### 완료된 작업 - [x] Phase 1: 프로젝트 셋업 - [x] Phase 2: 유틸리티 및 상수 - [x] Phase 3: Zustand 스토어 -- [x] Phase 4: API 계층 (공개 API만) -- [x] Phase 5: 커스텀 훅 (일부) -- [x] Phase 6: 공통 컴포넌트 (일부) - -### 구조 리팩토링 필요 -- [ ] 기존 코드를 새 폴더 구조로 재배치 -- [ ] App.jsx를 BrowserView/MobileView 구조로 변경 -- [ ] 레이아웃 컴포넌트 분리 (components/pc, components/mobile) +- [x] Phase 4: API 계층 (공개 API) +- [x] Phase 5: 커스텀 훅 +- [x] Phase 6: 공통 컴포넌트 +- [x] Phase 7: PC/Mobile 레이아웃 컴포넌트 +- [x] Phase 8: App.jsx (BrowserView/MobileView 라우팅) +- [x] Phase 9: 공개 페이지 전체 + - Home, Members, Album, AlbumDetail, AlbumGallery, TrackDetail + - Schedule, ScheduleDetail, Birthday ### 미완료 작업 -- [ ] 관리자 API 전체 -- [ ] 관리자 컴포넌트 전체 -- [ ] 관리자 페이지 전체 -- [ ] 누락된 공통 컴포넌트 (LightboxIndicator, Tooltip, ScrollToTop) -- [ ] 누락된 페이지 (AlbumDetail, AlbumGallery, TrackDetail, ScheduleDetail, Birthday) -- [ ] 누락된 훅 (useToast) -- [ ] PC/Mobile 페이지 분리 + +#### 공개 영역 +- [ ] NotFound 페이지 (pc/mobile) +- [ ] useToast 훅 + +#### 관리자 영역 (별도 요청 시 진행) +- [ ] 관리자 API 전체 (api/admin/) +- [ ] 관리자 컴포넌트 전체 (components/admin/) +- [ ] 관리자 페이지 전체 (pages/admin/) ### 최종 검증 - [ ] 모든 라우트 동작 확인 diff --git a/frontend-temp/src/components/mobile/BirthdayCard.jsx b/frontend-temp/src/components/mobile/BirthdayCard.jsx new file mode 100644 index 0000000..ce24f86 --- /dev/null +++ b/frontend-temp/src/components/mobile/BirthdayCard.jsx @@ -0,0 +1,84 @@ +import { motion } from 'framer-motion'; +import { dayjs, decodeHtmlEntities } from '@/utils'; + +/** + * Mobile용 생일 카드 컴포넌트 + * @param {Object} schedule - 일정 데이터 + * @param {boolean} showYear - 년도 표시 여부 + * @param {number} delay - 애니메이션 딜레이 (초) + * @param {function} onClick - 클릭 핸들러 + */ +function BirthdayCard({ schedule, showYear = false, delay = 0, onClick }) { + const scheduleDate = dayjs(schedule.date); + const formatted = { + year: scheduleDate.year(), + month: scheduleDate.month() + 1, + day: scheduleDate.date(), + }; + + const CardContent = ( +
+ {/* 배경 장식 */} +
+
+
+
🎉
+
+ +
+ {/* 멤버 사진 */} + {schedule.member_image && ( +
+
+ {schedule.member_names} +
+
+ )} + + {/* 내용 */} +
+ 🎂 +

+ {decodeHtmlEntities(schedule.title)} +

+
+ + {/* 날짜 뱃지 (showYear가 true일 때만 표시) */} + {showYear && ( +
+
{formatted.year}
+
{formatted.month}월
+
{formatted.day}
+
+ )} +
+
+ ); + + // delay가 있으면 motion 사용 + if (delay > 0) { + return ( + + {CardContent} + + ); + } + + return ( +
+ {CardContent} +
+ ); +} + +export default BirthdayCard; diff --git a/frontend-temp/src/components/schedule/MobileCalendar.jsx b/frontend-temp/src/components/mobile/Calendar.jsx similarity index 99% rename from frontend-temp/src/components/schedule/MobileCalendar.jsx rename to frontend-temp/src/components/mobile/Calendar.jsx index cbcd1f0..90b16ce 100644 --- a/frontend-temp/src/components/schedule/MobileCalendar.jsx +++ b/frontend-temp/src/components/mobile/Calendar.jsx @@ -5,7 +5,7 @@ import { getCategoryInfo } from '@/utils'; import { MIN_YEAR, WEEKDAYS } from '@/constants'; /** - * 모바일 달력 컴포넌트 (팝업형) + * Mobile용 달력 컴포넌트 (팝업형) * @param {Date} selectedDate - 선택된 날짜 * @param {Array} schedules - 일정 목록 (점 표시용) * @param {function} onSelectDate - 날짜 선택 핸들러 @@ -15,7 +15,7 @@ import { MIN_YEAR, WEEKDAYS } from '@/constants'; * @param {boolean} externalShowYearMonth - 외부에서 제어하는 년월 선택 모드 * @param {function} onShowYearMonthChange - 년월 선택 모드 변경 콜백 */ -function MobileCalendar({ +function Calendar({ selectedDate, schedules = [], onSelectDate, @@ -380,4 +380,4 @@ function MobileCalendar({ ); } -export default MobileCalendar; +export default Calendar; diff --git a/frontend-temp/src/components/schedule/MobileScheduleCard.jsx b/frontend-temp/src/components/mobile/ScheduleCard.jsx similarity index 95% rename from frontend-temp/src/components/schedule/MobileScheduleCard.jsx rename to frontend-temp/src/components/mobile/ScheduleCard.jsx index 38cf6b9..8099824 100644 --- a/frontend-temp/src/components/schedule/MobileScheduleCard.jsx +++ b/frontend-temp/src/components/mobile/ScheduleCard.jsx @@ -2,11 +2,11 @@ import { Clock, Tag, Link2 } from 'lucide-react'; import { decodeHtmlEntities, getDisplayMembers, getCategoryInfo, getScheduleTime } from '@/utils'; /** - * Mobile 일정 카드 컴포넌트 (홈용) + * Mobile용 일정 카드 컴포넌트 (홈용) * 홈 페이지의 다가오는 일정 섹션에서 사용 * 간결한 레이아웃 */ -function MobileScheduleCard({ schedule, onClick, className = '' }) { +function ScheduleCard({ schedule, onClick, className = '' }) { const scheduleDate = new Date(schedule.date); const today = new Date(); const currentYear = today.getFullYear(); @@ -96,4 +96,4 @@ function MobileScheduleCard({ schedule, onClick, className = '' }) { ); } -export default MobileScheduleCard; +export default ScheduleCard; diff --git a/frontend-temp/src/components/schedule/MobileScheduleListCard.jsx b/frontend-temp/src/components/mobile/ScheduleListCard.jsx similarity index 95% rename from frontend-temp/src/components/schedule/MobileScheduleListCard.jsx rename to frontend-temp/src/components/mobile/ScheduleListCard.jsx index 543cb21..cc17516 100644 --- a/frontend-temp/src/components/schedule/MobileScheduleListCard.jsx +++ b/frontend-temp/src/components/mobile/ScheduleListCard.jsx @@ -3,11 +3,11 @@ import { Clock, Link2 } from 'lucide-react'; import { decodeHtmlEntities, getDisplayMembers, getCategoryInfo, getScheduleTime } from '@/utils'; /** - * Mobile 일정 리스트 카드 컴포넌트 (타임라인용) + * Mobile용 일정 리스트 카드 컴포넌트 (타임라인용) * 스케줄 페이지에서 날짜별 일정 목록에 사용 * 날짜가 이미 헤더에 표시되므로 날짜 없이 표시 */ -function MobileScheduleListCard({ +function ScheduleListCard({ schedule, onClick, delay = 0, @@ -83,4 +83,4 @@ function MobileScheduleListCard({ ); } -export default MobileScheduleListCard; +export default ScheduleListCard; diff --git a/frontend-temp/src/components/schedule/MobileScheduleSearchCard.jsx b/frontend-temp/src/components/mobile/ScheduleSearchCard.jsx similarity index 97% rename from frontend-temp/src/components/schedule/MobileScheduleSearchCard.jsx rename to frontend-temp/src/components/mobile/ScheduleSearchCard.jsx index 285222e..1c405da 100644 --- a/frontend-temp/src/components/schedule/MobileScheduleSearchCard.jsx +++ b/frontend-temp/src/components/mobile/ScheduleSearchCard.jsx @@ -3,11 +3,11 @@ import { Clock, Link2 } from 'lucide-react'; import { decodeHtmlEntities, getDisplayMembers, getCategoryInfo, getScheduleTime } from '@/utils'; /** - * Mobile 일정 검색 카드 컴포넌트 + * Mobile용 일정 검색 카드 컴포넌트 * 스케줄 페이지의 검색 결과에서 사용 * 날짜를 왼쪽에 표시하는 레이아웃 */ -function MobileScheduleSearchCard({ +function ScheduleSearchCard({ schedule, onClick, delay = 0, @@ -112,4 +112,4 @@ function MobileScheduleSearchCard({ ); } -export default MobileScheduleSearchCard; +export default ScheduleSearchCard; diff --git a/frontend-temp/src/components/mobile/index.js b/frontend-temp/src/components/mobile/index.js index 6c48fae..4533f85 100644 --- a/frontend-temp/src/components/mobile/index.js +++ b/frontend-temp/src/components/mobile/index.js @@ -1 +1,9 @@ +// 레이아웃 export { default as Layout } from './Layout'; + +// 일정 컴포넌트 +export { default as Calendar } from './Calendar'; +export { default as ScheduleCard } from './ScheduleCard'; +export { default as ScheduleListCard } from './ScheduleListCard'; +export { default as ScheduleSearchCard } from './ScheduleSearchCard'; +export { default as BirthdayCard } from './BirthdayCard'; diff --git a/frontend-temp/src/components/pc/BirthdayCard.jsx b/frontend-temp/src/components/pc/BirthdayCard.jsx new file mode 100644 index 0000000..6cc7dc8 --- /dev/null +++ b/frontend-temp/src/components/pc/BirthdayCard.jsx @@ -0,0 +1,60 @@ +import { dayjs } from '@/utils'; + +/** + * PC용 생일 카드 컴포넌트 + */ +function BirthdayCard({ schedule, showYear = false, onClick }) { + const scheduleDate = dayjs(schedule.date); + const formatted = { + year: scheduleDate.year(), + month: scheduleDate.month() + 1, + day: scheduleDate.date(), + }; + + return ( +
+ {/* 배경 장식 */} +
+
+
+
+
🎉
+
+ +
+ {/* 멤버 사진 */} + {schedule.member_image && ( +
+
+ {schedule.member_names} +
+
+ )} + + {/* 내용 */} +
+ 🎂 +

{schedule.title}

+
+ + {/* 날짜 뱃지 */} +
+ {showYear && ( +
{formatted.year}
+ )} +
{formatted.month}월
+
{formatted.day}
+
+
+
+ ); +} + +export default BirthdayCard; diff --git a/frontend-temp/src/components/schedule/Calendar.jsx b/frontend-temp/src/components/pc/Calendar.jsx similarity index 99% rename from frontend-temp/src/components/schedule/Calendar.jsx rename to frontend-temp/src/components/pc/Calendar.jsx index e2fe616..ab35ff2 100644 --- a/frontend-temp/src/components/schedule/Calendar.jsx +++ b/frontend-temp/src/components/pc/Calendar.jsx @@ -7,7 +7,7 @@ import { MIN_YEAR, WEEKDAYS, MONTH_NAMES } from '@/constants'; const MONTHS = MONTH_NAMES; /** - * 달력 컴포넌트 + * PC용 달력 컴포넌트 * @param {Date} currentDate - 현재 표시 중인 년/월 * @param {function} onDateChange - 년/월 변경 핸들러 * @param {string} selectedDate - 선택된 날짜 (YYYY-MM-DD) diff --git a/frontend-temp/src/components/schedule/CategoryFilter.jsx b/frontend-temp/src/components/pc/CategoryFilter.jsx similarity index 98% rename from frontend-temp/src/components/schedule/CategoryFilter.jsx rename to frontend-temp/src/components/pc/CategoryFilter.jsx index aba5237..0119ff3 100644 --- a/frontend-temp/src/components/schedule/CategoryFilter.jsx +++ b/frontend-temp/src/components/pc/CategoryFilter.jsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { motion } from 'framer-motion'; /** - * 카테고리 필터 컴포넌트 + * PC용 카테고리 필터 컴포넌트 * @param {Array} categories - 카테고리 목록 * @param {Array} selectedCategories - 선택된 카테고리 ID 목록 * @param {function} onToggle - 카테고리 토글 핸들러 diff --git a/frontend-temp/src/components/schedule/ScheduleCard.jsx b/frontend-temp/src/components/pc/ScheduleCard.jsx similarity index 98% rename from frontend-temp/src/components/schedule/ScheduleCard.jsx rename to frontend-temp/src/components/pc/ScheduleCard.jsx index f19d026..9dfe4c6 100644 --- a/frontend-temp/src/components/schedule/ScheduleCard.jsx +++ b/frontend-temp/src/components/pc/ScheduleCard.jsx @@ -2,7 +2,7 @@ import { Clock, Tag, Link2 } from 'lucide-react'; import { decodeHtmlEntities, getDisplayMembers, getCategoryInfo, getScheduleTime } from '@/utils'; /** - * PC 일정 카드 컴포넌트 (일반용) + * PC용 일정 카드 컴포넌트 * 홈, 스케줄 페이지에서 공통으로 사용 */ function ScheduleCard({ schedule, onClick, className = '' }) { diff --git a/frontend-temp/src/components/pc/index.js b/frontend-temp/src/components/pc/index.js index 85bb816..a128ea5 100644 --- a/frontend-temp/src/components/pc/index.js +++ b/frontend-temp/src/components/pc/index.js @@ -1,3 +1,10 @@ +// 레이아웃 export { default as Layout } from './Layout'; export { default as Header } from './Header'; export { default as Footer } from './Footer'; + +// 일정 컴포넌트 +export { default as Calendar } from './Calendar'; +export { default as ScheduleCard } from './ScheduleCard'; +export { default as BirthdayCard } from './BirthdayCard'; +export { default as CategoryFilter } from './CategoryFilter'; diff --git a/frontend-temp/src/components/schedule/BirthdayCard.jsx b/frontend-temp/src/components/schedule/BirthdayCard.jsx deleted file mode 100644 index bd4afda..0000000 --- a/frontend-temp/src/components/schedule/BirthdayCard.jsx +++ /dev/null @@ -1,200 +0,0 @@ -import { motion } from 'framer-motion'; -import confetti from 'canvas-confetti'; -import { dayjs, decodeHtmlEntities } from '@/utils'; - -/** - * 생일 폭죽 애니메이션 - */ -export function fireBirthdayConfetti() { - const duration = 3000; - const animationEnd = Date.now() + duration; - const colors = ['#ff69b4', '#ff1493', '#da70d6', '#ba55d3', '#9370db', '#8a2be2', '#ffd700', '#ff6347']; - - const randomInRange = (min, max) => Math.random() * (max - min) + min; - - const interval = setInterval(() => { - const timeLeft = animationEnd - Date.now(); - - if (timeLeft <= 0) { - clearInterval(interval); - return; - } - - const particleCount = 50 * (timeLeft / duration); - - // 왼쪽에서 발사 - confetti({ - particleCount: Math.floor(particleCount), - startVelocity: 30, - spread: 60, - origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }, - colors, - shapes: ['circle', 'square'], - gravity: 1.2, - scalar: randomInRange(0.8, 1.2), - drift: randomInRange(-0.5, 0.5), - }); - - // 오른쪽에서 발사 - confetti({ - particleCount: Math.floor(particleCount), - startVelocity: 30, - spread: 60, - origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }, - colors, - shapes: ['circle', 'square'], - gravity: 1.2, - scalar: randomInRange(0.8, 1.2), - drift: randomInRange(-0.5, 0.5), - }); - }, 250); - - // 초기 대형 폭죽 - confetti({ - particleCount: 100, - spread: 100, - origin: { x: 0.5, y: 0.6 }, - colors, - shapes: ['circle', 'square'], - startVelocity: 45, - }); -} - -/** - * PC용 생일 카드 컴포넌트 - */ -function BirthdayCard({ schedule, showYear = false, onClick }) { - const scheduleDate = dayjs(schedule.date); - const formatted = { - year: scheduleDate.year(), - month: scheduleDate.month() + 1, - day: scheduleDate.date(), - }; - - return ( -
- {/* 배경 장식 */} -
-
-
-
-
🎉
-
- -
- {/* 멤버 사진 */} - {schedule.member_image && ( -
-
- {schedule.member_names} -
-
- )} - - {/* 내용 */} -
- 🎂 -

{schedule.title}

-
- - {/* 날짜 뱃지 */} -
- {showYear && ( -
{formatted.year}
- )} -
{formatted.month}월
-
{formatted.day}
-
-
-
- ); -} - -/** - * Mobile용 생일 카드 컴포넌트 - * @param {Object} schedule - 일정 데이터 - * @param {boolean} showYear - 년도 표시 여부 - * @param {number} delay - 애니메이션 딜레이 (초) - * @param {function} onClick - 클릭 핸들러 - */ -export function MobileBirthdayCard({ schedule, showYear = false, delay = 0, onClick }) { - const scheduleDate = dayjs(schedule.date); - const formatted = { - year: scheduleDate.year(), - month: scheduleDate.month() + 1, - day: scheduleDate.date(), - }; - - const CardContent = ( -
- {/* 배경 장식 */} -
-
-
-
🎉
-
- -
- {/* 멤버 사진 */} - {schedule.member_image && ( -
-
- {schedule.member_names} -
-
- )} - - {/* 내용 */} -
- 🎂 -

- {decodeHtmlEntities(schedule.title)} -

-
- - {/* 날짜 뱃지 (showYear가 true일 때만 표시) */} - {showYear && ( -
-
{formatted.year}
-
{formatted.month}월
-
{formatted.day}
-
- )} -
-
- ); - - // delay가 있으면 motion 사용 - if (delay > 0) { - return ( - - {CardContent} - - ); - } - - return ( -
- {CardContent} -
- ); -} - -export default BirthdayCard; diff --git a/frontend-temp/src/components/schedule/confetti.js b/frontend-temp/src/components/schedule/confetti.js new file mode 100644 index 0000000..8d04b83 --- /dev/null +++ b/frontend-temp/src/components/schedule/confetti.js @@ -0,0 +1,60 @@ +import confetti from 'canvas-confetti'; + +/** + * 생일 폭죽 애니메이션 + * PC/Mobile 공용 + */ +export function fireBirthdayConfetti() { + const duration = 3000; + const animationEnd = Date.now() + duration; + const colors = ['#ff69b4', '#ff1493', '#da70d6', '#ba55d3', '#9370db', '#8a2be2', '#ffd700', '#ff6347']; + + const randomInRange = (min, max) => Math.random() * (max - min) + min; + + const interval = setInterval(() => { + const timeLeft = animationEnd - Date.now(); + + if (timeLeft <= 0) { + clearInterval(interval); + return; + } + + const particleCount = 50 * (timeLeft / duration); + + // 왼쪽에서 발사 + confetti({ + particleCount: Math.floor(particleCount), + startVelocity: 30, + spread: 60, + origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }, + colors, + shapes: ['circle', 'square'], + gravity: 1.2, + scalar: randomInRange(0.8, 1.2), + drift: randomInRange(-0.5, 0.5), + }); + + // 오른쪽에서 발사 + confetti({ + particleCount: Math.floor(particleCount), + startVelocity: 30, + spread: 60, + origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }, + colors, + shapes: ['circle', 'square'], + gravity: 1.2, + scalar: randomInRange(0.8, 1.2), + drift: randomInRange(-0.5, 0.5), + }); + }, 250); + + // 초기 대형 폭죽 + confetti({ + particleCount: 100, + spread: 100, + origin: { x: 0.5, y: 0.6 }, + colors, + shapes: ['circle', 'square'], + startVelocity: 45, + }); +} diff --git a/frontend-temp/src/components/schedule/index.js b/frontend-temp/src/components/schedule/index.js index a4838aa..9297f60 100644 --- a/frontend-temp/src/components/schedule/index.js +++ b/frontend-temp/src/components/schedule/index.js @@ -1,14 +1,5 @@ -// PC 컴포넌트 -export { default as ScheduleCard } from './ScheduleCard'; +// 공용 함수 +export { fireBirthdayConfetti } from './confetti'; + +// 관리자 컴포넌트 export { default as AdminScheduleCard } from './AdminScheduleCard'; -export { default as Calendar } from './Calendar'; - -// Mobile 컴포넌트 -export { default as MobileScheduleCard } from './MobileScheduleCard'; -export { default as MobileScheduleListCard } from './MobileScheduleListCard'; -export { default as MobileScheduleSearchCard } from './MobileScheduleSearchCard'; -export { default as MobileCalendar } from './MobileCalendar'; - -// 공통 컴포넌트 -export { default as CategoryFilter } from './CategoryFilter'; -export { default as BirthdayCard, MobileBirthdayCard, fireBirthdayConfetti } from './BirthdayCard'; diff --git a/frontend-temp/src/pages/home/mobile/Home.jsx b/frontend-temp/src/pages/home/mobile/Home.jsx index 46f87e8..f628e7d 100644 --- a/frontend-temp/src/pages/home/mobile/Home.jsx +++ b/frontend-temp/src/pages/home/mobile/Home.jsx @@ -2,7 +2,7 @@ import { motion } from 'framer-motion'; import { ChevronRight } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { useMembers, useAlbums, useUpcomingSchedules } from '@/hooks'; -import { MobileScheduleCard } from '@/components'; +import { ScheduleCard as MobileScheduleCard } from '@/components/mobile'; /** * Mobile 홈 페이지 diff --git a/frontend-temp/src/pages/home/pc/Home.jsx b/frontend-temp/src/pages/home/pc/Home.jsx index 527e1e7..86d1d89 100644 --- a/frontend-temp/src/pages/home/pc/Home.jsx +++ b/frontend-temp/src/pages/home/pc/Home.jsx @@ -2,7 +2,7 @@ import { motion } from 'framer-motion'; import { Link } from 'react-router-dom'; import { Calendar, ArrowRight, Music } from 'lucide-react'; import { useMembers, useAlbums, useUpcomingSchedules } from '@/hooks'; -import { ScheduleCard } from '@/components'; +import { ScheduleCard } from '@/components/pc'; /** * PC 홈 페이지 diff --git a/frontend-temp/src/pages/schedule/mobile/Schedule.jsx b/frontend-temp/src/pages/schedule/mobile/Schedule.jsx index 27d4ac9..6df4800 100644 --- a/frontend-temp/src/pages/schedule/mobile/Schedule.jsx +++ b/frontend-temp/src/pages/schedule/mobile/Schedule.jsx @@ -11,12 +11,12 @@ import { getSchedules, searchSchedules } from '@/api/schedules'; import { useScheduleStore } from '@/stores'; import { MIN_YEAR, SEARCH_LIMIT } from '@/constants'; import { - MobileCalendar, - MobileScheduleListCard, - MobileScheduleSearchCard, - MobileBirthdayCard, - fireBirthdayConfetti, -} from '@/components/schedule'; + Calendar as MobileCalendar, + ScheduleListCard as MobileScheduleListCard, + ScheduleSearchCard as MobileScheduleSearchCard, + BirthdayCard as MobileBirthdayCard, +} from '@/components/mobile'; +import { fireBirthdayConfetti } from '@/components/schedule'; /** * 모바일 일정 페이지 diff --git a/frontend-temp/src/pages/schedule/pc/Schedule.jsx b/frontend-temp/src/pages/schedule/pc/Schedule.jsx index 73139cf..ef6a0e8 100644 --- a/frontend-temp/src/pages/schedule/pc/Schedule.jsx +++ b/frontend-temp/src/pages/schedule/pc/Schedule.jsx @@ -11,8 +11,8 @@ import { CategoryFilter, ScheduleCard, BirthdayCard, - fireBirthdayConfetti, -} from '@/components/schedule'; +} from '@/components/pc'; +import { fireBirthdayConfetti } from '@/components/schedule'; import { getSchedules, searchSchedules } from '@/api/schedules'; import { useScheduleStore } from '@/stores'; import { getTodayKST } from '@/utils';