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 && (
+
+
+

+
+
+ )}
+
+ {/* 내용 */}
+
+ 🎂
+
+ {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.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.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 && (
-
-
-

-
-
- )}
-
- {/* 내용 */}
-
- 🎂
-
- {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';