fix(backend): getUpcomingSchedules 응답 형식 통일
- getUpcomingSchedules가 getMonthlySchedules와 동일한 날짜별 그룹화 형식 반환 - routes/schedules 응답 스키마에 oneOf 추가 (객체/배열 둘 다 허용) - docs/architecture.md, migration.md 업데이트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bd8e87f636
commit
e0ab3ce0f8
4 changed files with 501 additions and 34 deletions
|
|
@ -58,7 +58,12 @@ export default async function schedulesRoutes(fastify) {
|
|||
description: 'search 파라미터로 검색, year/month로 월별 조회, startDate로 다가오는 일정 조회',
|
||||
querystring: scheduleSearchQuery,
|
||||
response: {
|
||||
200: { type: 'object', additionalProperties: true },
|
||||
200: {
|
||||
oneOf: [
|
||||
{ type: 'object', additionalProperties: true },
|
||||
{ type: 'array', items: { type: 'object', additionalProperties: true } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}, async (request, reply) => {
|
||||
|
|
|
|||
|
|
@ -302,12 +302,14 @@ export async function getMonthlySchedules(db, year, month) {
|
|||
|
||||
/**
|
||||
* 다가오는 일정 조회 (startDate부터 limit개)
|
||||
* getMonthlySchedules와 동일한 형식으로 반환
|
||||
* @param {object} db - 데이터베이스 연결
|
||||
* @param {string} startDate - 시작 날짜
|
||||
* @param {number} limit - 조회 개수
|
||||
* @returns {array} 일정 목록
|
||||
* @returns {object} 날짜별로 그룹화된 일정
|
||||
*/
|
||||
export async function getUpcomingSchedules(db, startDate, limit) {
|
||||
// 일정 조회 (YouTube, X 소스 정보 포함)
|
||||
const [schedules] = await db.query(`
|
||||
SELECT
|
||||
s.id,
|
||||
|
|
@ -316,9 +318,15 @@ export async function getUpcomingSchedules(db, startDate, limit) {
|
|||
s.time,
|
||||
s.category_id,
|
||||
c.name as category_name,
|
||||
c.color as category_color
|
||||
c.color as category_color,
|
||||
sy.channel_name as youtube_channel,
|
||||
sy.video_id as youtube_video_id,
|
||||
sy.video_type as youtube_video_type,
|
||||
sx.post_id as x_post_id
|
||||
FROM schedules s
|
||||
LEFT JOIN schedule_categories c ON s.category_id = c.id
|
||||
LEFT JOIN schedule_youtube sy ON s.id = sy.schedule_id
|
||||
LEFT JOIN schedule_x sx ON s.id = sx.schedule_id
|
||||
WHERE s.date >= ?
|
||||
ORDER BY s.date ASC, s.time ASC
|
||||
LIMIT ?
|
||||
|
|
@ -345,22 +353,70 @@ export async function getUpcomingSchedules(db, startDate, limit) {
|
|||
}
|
||||
}
|
||||
|
||||
// 결과 포맷팅
|
||||
return schedules.map(s => {
|
||||
// 날짜별로 그룹화 (getMonthlySchedules와 동일한 형식)
|
||||
const grouped = {};
|
||||
|
||||
for (const s of schedules) {
|
||||
const dateKey = s.date instanceof Date
|
||||
? s.date.toISOString().split('T')[0]
|
||||
: s.date;
|
||||
|
||||
if (!grouped[dateKey]) {
|
||||
grouped[dateKey] = {
|
||||
categories: [],
|
||||
schedules: [],
|
||||
};
|
||||
}
|
||||
|
||||
// 멤버 정보 (5명 이상이면 프로미스나인)
|
||||
const scheduleMembers = memberMap[s.id] || [];
|
||||
const members = scheduleMembers.length >= 5
|
||||
? [{ name: '프로미스나인' }]
|
||||
: scheduleMembers;
|
||||
|
||||
return {
|
||||
const schedule = {
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
date: s.date,
|
||||
time: s.time,
|
||||
category_id: s.category_id,
|
||||
category_name: s.category_name,
|
||||
category_color: s.category_color,
|
||||
category: {
|
||||
id: s.category_id,
|
||||
name: s.category_name,
|
||||
color: s.category_color,
|
||||
},
|
||||
members,
|
||||
};
|
||||
|
||||
// source 정보 추가
|
||||
if (s.category_id === CATEGORY_IDS.YOUTUBE && s.youtube_video_id) {
|
||||
const videoUrl = s.youtube_video_type === 'shorts'
|
||||
? `https://www.youtube.com/shorts/${s.youtube_video_id}`
|
||||
: `https://www.youtube.com/watch?v=${s.youtube_video_id}`;
|
||||
schedule.source = {
|
||||
name: s.youtube_channel || 'YouTube',
|
||||
url: videoUrl,
|
||||
};
|
||||
} else if (s.category_id === CATEGORY_IDS.X && s.x_post_id) {
|
||||
schedule.source = {
|
||||
name: '',
|
||||
url: `https://x.com/${config.x.defaultUsername}/status/${s.x_post_id}`,
|
||||
};
|
||||
}
|
||||
|
||||
grouped[dateKey].schedules.push(schedule);
|
||||
|
||||
// 카테고리 카운트
|
||||
const existingCategory = grouped[dateKey].categories.find(c => c.id === s.category_id);
|
||||
if (existingCategory) {
|
||||
existingCategory.count++;
|
||||
} else {
|
||||
grouped[dateKey].categories.push({
|
||||
id: s.category_id,
|
||||
name: s.category_name,
|
||||
color: s.category_color,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,36 +45,163 @@ fromis_9/
|
|||
│
|
||||
├── backend-backup/ # Express 백엔드 (참조용, 마이그레이션 원본)
|
||||
│
|
||||
├── frontend/ # React 프론트엔드
|
||||
├── frontend/ # React 프론트엔드 (레거시, frontend-temp로 대체 예정)
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # API 클라이언트
|
||||
│ │ │ ├── index.js # fetchApi 유틸
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── public/ # 공개 API
|
||||
│ │ │ │ ├── albums.js
|
||||
│ │ │ │ ├── members.js
|
||||
│ │ │ │ └── schedules.js
|
||||
│ │ │ └── admin/ # 어드민 API
|
||||
│ │ │ ├── albums.js
|
||||
│ │ │ ├── auth.js
|
||||
│ │ │ ├── bots.js
|
||||
│ │ │ ├── categories.js
|
||||
│ │ │ ├── members.js
|
||||
│ │ │ ├── schedules.js
|
||||
│ │ │ ├── stats.js
|
||||
│ │ │ └── suggestions.js
|
||||
│ │ ├── components/ # 공통 컴포넌트
|
||||
│ │ │ └── common/
|
||||
│ │ │ ├── Lightbox.jsx # 이미지 라이트박스 (PC)
|
||||
│ │ │ └── LightboxIndicator.jsx
|
||||
│ │ ├── pages/
|
||||
│ │ │ ├── pc/ # PC 페이지
|
||||
│ │ │ └── mobile/ # 모바일 페이지
|
||||
│ │ ├── stores/ # Zustand 스토어
|
||||
│ │ ├── utils/
|
||||
│ │ │ └── date.js # dayjs 기반 날짜 유틸리티
|
||||
│ │ └── App.jsx
|
||||
│ │ └── ...
|
||||
│ └── package.json
|
||||
│
|
||||
├── frontend-temp/ # React 프론트엔드 (신규, Strangler Fig 마이그레이션)
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # API 클라이언트 (공유)
|
||||
│ │ │ ├── index.js
|
||||
│ │ │ ├── client.js # fetchApi, fetchAuthApi
|
||||
│ │ │ ├── albums.js
|
||||
│ │ │ ├── members.js
|
||||
│ │ │ ├── schedules.js
|
||||
│ │ │ ├── auth.js
|
||||
│ │ │ └── admin/ # 관리자 API
|
||||
│ │ │ ├── albums.js
|
||||
│ │ │ ├── members.js
|
||||
│ │ │ ├── schedules.js
|
||||
│ │ │ ├── categories.js
|
||||
│ │ │ ├── stats.js
|
||||
│ │ │ ├── bots.js
|
||||
│ │ │ └── suggestions.js
|
||||
│ │ │
|
||||
│ │ ├── hooks/ # 커스텀 훅 (공유)
|
||||
│ │ │ ├── index.js
|
||||
│ │ │ ├── useAlbumData.js
|
||||
│ │ │ ├── useMemberData.js
|
||||
│ │ │ ├── useScheduleData.js
|
||||
│ │ │ ├── useScheduleSearch.js
|
||||
│ │ │ ├── useCalendar.js
|
||||
│ │ │ ├── useToast.js
|
||||
│ │ │ └── useAdminAuth.js
|
||||
│ │ │
|
||||
│ │ ├── stores/ # Zustand 스토어 (공유)
|
||||
│ │ │ ├── index.js
|
||||
│ │ │ ├── useScheduleStore.js
|
||||
│ │ │ └── useAuthStore.js
|
||||
│ │ │
|
||||
│ │ ├── utils/ # 유틸리티 (공유)
|
||||
│ │ │ ├── index.js
|
||||
│ │ │ ├── date.js
|
||||
│ │ │ └── format.js
|
||||
│ │ │
|
||||
│ │ ├── constants/
|
||||
│ │ │ └── index.js
|
||||
│ │ │
|
||||
│ │ ├── components/
|
||||
│ │ │ ├── index.js
|
||||
│ │ │ ├── common/ # 디바이스 무관 공통 컴포넌트
|
||||
│ │ │ │ ├── Loading.jsx
|
||||
│ │ │ │ ├── ErrorBoundary.jsx
|
||||
│ │ │ │ ├── Toast.jsx
|
||||
│ │ │ │ ├── Lightbox.jsx
|
||||
│ │ │ │ ├── LightboxIndicator.jsx
|
||||
│ │ │ │ ├── Tooltip.jsx
|
||||
│ │ │ │ └── ScrollToTop.jsx
|
||||
│ │ │ ├── pc/ # PC 레이아웃 컴포넌트
|
||||
│ │ │ │ ├── Layout.jsx
|
||||
│ │ │ │ ├── Header.jsx
|
||||
│ │ │ │ └── Footer.jsx
|
||||
│ │ │ ├── mobile/ # Mobile 레이아웃 컴포넌트
|
||||
│ │ │ │ ├── Layout.jsx
|
||||
│ │ │ │ └── MobileNav.jsx
|
||||
│ │ │ └── admin/ # 관리자 컴포넌트
|
||||
│ │ │ ├── AdminLayout.jsx
|
||||
│ │ │ ├── AdminHeader.jsx
|
||||
│ │ │ ├── ConfirmDialog.jsx
|
||||
│ │ │ ├── CustomDatePicker.jsx
|
||||
│ │ │ ├── CustomTimePicker.jsx
|
||||
│ │ │ └── NumberPicker.jsx
|
||||
│ │ │
|
||||
│ │ ├── pages/
|
||||
│ │ │ ├── index.js
|
||||
│ │ │ │
|
||||
│ │ │ ├── home/
|
||||
│ │ │ │ ├── index.js # export { PCHome, MobileHome }
|
||||
│ │ │ │ ├── pc/
|
||||
│ │ │ │ │ └── Home.jsx
|
||||
│ │ │ │ └── mobile/
|
||||
│ │ │ │ └── Home.jsx
|
||||
│ │ │ │
|
||||
│ │ │ ├── members/
|
||||
│ │ │ │ ├── index.js
|
||||
│ │ │ │ ├── pc/
|
||||
│ │ │ │ │ └── Members.jsx
|
||||
│ │ │ │ └── mobile/
|
||||
│ │ │ │ └── Members.jsx
|
||||
│ │ │ │
|
||||
│ │ │ ├── album/
|
||||
│ │ │ │ ├── index.js
|
||||
│ │ │ │ ├── pc/
|
||||
│ │ │ │ │ ├── Album.jsx
|
||||
│ │ │ │ │ ├── AlbumDetail.jsx
|
||||
│ │ │ │ │ ├── AlbumGallery.jsx
|
||||
│ │ │ │ │ └── TrackDetail.jsx
|
||||
│ │ │ │ └── mobile/
|
||||
│ │ │ │ ├── Album.jsx
|
||||
│ │ │ │ ├── AlbumDetail.jsx
|
||||
│ │ │ │ ├── AlbumGallery.jsx
|
||||
│ │ │ │ └── TrackDetail.jsx
|
||||
│ │ │ │
|
||||
│ │ │ ├── schedule/
|
||||
│ │ │ │ ├── index.js
|
||||
│ │ │ │ ├── sections/ # 일정 상세 섹션 (PC 전용)
|
||||
│ │ │ │ │ ├── DefaultSection.jsx
|
||||
│ │ │ │ │ ├── XSection.jsx
|
||||
│ │ │ │ │ └── YoutubeSection.jsx
|
||||
│ │ │ │ ├── pc/
|
||||
│ │ │ │ │ ├── Schedule.jsx
|
||||
│ │ │ │ │ ├── ScheduleDetail.jsx
|
||||
│ │ │ │ │ └── Birthday.jsx
|
||||
│ │ │ │ └── mobile/
|
||||
│ │ │ │ ├── Schedule.jsx
|
||||
│ │ │ │ └── ScheduleDetail.jsx
|
||||
│ │ │ │
|
||||
│ │ │ ├── common/
|
||||
│ │ │ │ ├── pc/
|
||||
│ │ │ │ │ └── NotFound.jsx
|
||||
│ │ │ │ └── mobile/
|
||||
│ │ │ │ └── NotFound.jsx
|
||||
│ │ │ │
|
||||
│ │ │ └── admin/ # 관리자 페이지 (PC 전용)
|
||||
│ │ │ ├── index.js
|
||||
│ │ │ ├── Login.jsx
|
||||
│ │ │ ├── Dashboard.jsx
|
||||
│ │ │ ├── members/
|
||||
│ │ │ │ ├── List.jsx
|
||||
│ │ │ │ └── Edit.jsx
|
||||
│ │ │ ├── albums/
|
||||
│ │ │ │ ├── List.jsx
|
||||
│ │ │ │ ├── Form.jsx
|
||||
│ │ │ │ └── Photos.jsx
|
||||
│ │ │ ├── schedules/
|
||||
│ │ │ │ ├── List.jsx
|
||||
│ │ │ │ ├── Form.jsx
|
||||
│ │ │ │ ├── YouTubeForm.jsx
|
||||
│ │ │ │ ├── XForm.jsx
|
||||
│ │ │ │ └── YouTubeEditForm.jsx
|
||||
│ │ │ ├── categories/
|
||||
│ │ │ │ └── List.jsx
|
||||
│ │ │ ├── bots/
|
||||
│ │ │ │ └── Manager.jsx
|
||||
│ │ │ └── dict/
|
||||
│ │ │ └── Manager.jsx
|
||||
│ │ │
|
||||
│ │ ├── App.jsx # BrowserView/MobileView 라우팅
|
||||
│ │ └── main.jsx
|
||||
│ │
|
||||
│ ├── vite.config.js
|
||||
│ ├── Dockerfile # 프론트엔드 컨테이너
|
||||
│ ├── tailwind.config.js
|
||||
│ ├── Dockerfile
|
||||
│ └── package.json
|
||||
│
|
||||
├── docker-compose.yml
|
||||
|
|
|
|||
|
|
@ -144,3 +144,282 @@
|
|||
- DB 스키마 변경 사항:
|
||||
- `tracks` → `album_tracks` (이름 변경)
|
||||
- `venues` → `concert_venues` (이름 변경)
|
||||
|
||||
---
|
||||
|
||||
# 프론트엔드 마이그레이션 (Strangler Fig)
|
||||
|
||||
## 개요
|
||||
|
||||
`frontend/` (레거시) → `frontend-temp/` (신규)로 점진적 마이그레이션
|
||||
|
||||
## 설계 원칙
|
||||
|
||||
1. **react-device-detect 사용**
|
||||
- App.jsx에서 `BrowserView`/`MobileView`로 PC/Mobile 완전 분리
|
||||
- User Agent 기반 디바이스 감지
|
||||
|
||||
2. **기능별 폴더 + pc/mobile 하위 폴더**
|
||||
- 각 페이지 폴더 내에 `pc/`, `mobile/` 서브폴더
|
||||
- 비즈니스 로직(api, hooks, stores)은 최상위에서 공유
|
||||
|
||||
3. **React Query 사용**
|
||||
- `useQuery`로 데이터 패칭 및 캐싱
|
||||
|
||||
4. **관리자 페이지는 PC 전용**
|
||||
- mobile 폴더 없이 단일 구조
|
||||
|
||||
## App.jsx 라우팅 구조
|
||||
|
||||
```jsx
|
||||
import { BrowserView, MobileView } from 'react-device-detect';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<BrowserView>
|
||||
<Routes>
|
||||
{/* Admin routes */}
|
||||
<Route path="/admin" element={<AdminLogin />} />
|
||||
<Route path="/admin/*" element={<AdminRoutes />} />
|
||||
|
||||
{/* Public routes with PC Layout */}
|
||||
<Route element={<PCLayout />}>
|
||||
<Route path="/" element={<PCHome />} />
|
||||
<Route path="/members" element={<PCMembers />} />
|
||||
<Route path="/album" element={<PCAlbum />} />
|
||||
<Route path="/album/:name" element={<PCAlbumDetail />} />
|
||||
...
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserView>
|
||||
|
||||
<MobileView>
|
||||
<Routes>
|
||||
<Route element={<MobileLayout />}>
|
||||
<Route path="/" element={<MobileHome />} />
|
||||
<Route path="/members" element={<MobileMembers />} />
|
||||
...
|
||||
</Route>
|
||||
</Routes>
|
||||
</MobileView>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 전체 마이그레이션 체크리스트
|
||||
|
||||
### API 계층
|
||||
|
||||
#### 공개 API
|
||||
- [x] client.js (fetchApi, fetchAuthApi)
|
||||
- [x] albums.js
|
||||
- [x] members.js
|
||||
- [x] schedules.js
|
||||
- [x] auth.js
|
||||
|
||||
#### 관리자 API (`api/admin/`)
|
||||
- [ ] albums.js
|
||||
- [ ] members.js
|
||||
- [ ] schedules.js
|
||||
- [ ] categories.js
|
||||
- [ ] stats.js
|
||||
- [ ] bots.js
|
||||
- [ ] suggestions.js
|
||||
|
||||
### 훅 (hooks/)
|
||||
- [x] useAlbumData.js
|
||||
- [x] useMemberData.js
|
||||
- [x] useScheduleData.js
|
||||
- [x] useScheduleSearch.js
|
||||
- [x] useCalendar.js
|
||||
- [x] useAdminAuth.js
|
||||
- [ ] useToast.js
|
||||
|
||||
### 스토어 (stores/)
|
||||
- [x] useScheduleStore.js
|
||||
- [x] useAuthStore.js
|
||||
|
||||
### 유틸리티 (utils/)
|
||||
- [x] date.js
|
||||
- [x] format.js
|
||||
|
||||
### 공통 컴포넌트 (components/common/)
|
||||
- [x] Loading.jsx
|
||||
- [x] ErrorBoundary.jsx
|
||||
- [x] Toast.jsx
|
||||
- [x] Lightbox.jsx
|
||||
- [ ] LightboxIndicator.jsx
|
||||
- [ ] Tooltip.jsx
|
||||
- [ ] ScrollToTop.jsx
|
||||
|
||||
### PC 레이아웃 컴포넌트 (components/pc/)
|
||||
- [ ] Layout.jsx (Outlet 사용)
|
||||
- [ ] Header.jsx
|
||||
- [ ] Footer.jsx
|
||||
|
||||
### Mobile 레이아웃 컴포넌트 (components/mobile/)
|
||||
- [ ] Layout.jsx (Outlet 사용)
|
||||
- [ ] MobileNav.jsx
|
||||
|
||||
### 관리자 컴포넌트 (components/admin/)
|
||||
- [ ] AdminLayout.jsx
|
||||
- [ ] AdminHeader.jsx
|
||||
- [ ] ConfirmDialog.jsx
|
||||
- [ ] CustomDatePicker.jsx
|
||||
- [ ] CustomTimePicker.jsx
|
||||
- [ ] NumberPicker.jsx
|
||||
|
||||
### 페이지 - Home (pages/home/)
|
||||
- [ ] pc/Home.jsx
|
||||
- [ ] mobile/Home.jsx
|
||||
|
||||
### 페이지 - Members (pages/members/)
|
||||
- [ ] pc/Members.jsx
|
||||
- [ ] 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
|
||||
|
||||
### 페이지 - 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
|
||||
|
||||
### 페이지 - Common (pages/common/)
|
||||
- [ ] pc/NotFound.jsx
|
||||
- [ ] mobile/NotFound.jsx
|
||||
|
||||
### 페이지 - Admin (pages/admin/) - PC 전용
|
||||
- [ ] Login.jsx
|
||||
- [ ] Dashboard.jsx
|
||||
- [ ] members/List.jsx
|
||||
- [ ] members/Edit.jsx
|
||||
- [ ] albums/List.jsx
|
||||
- [ ] albums/Form.jsx
|
||||
- [ ] albums/Photos.jsx
|
||||
- [ ] schedules/List.jsx
|
||||
- [ ] schedules/Form.jsx
|
||||
- [ ] schedules/YouTubeForm.jsx
|
||||
- [ ] schedules/XForm.jsx
|
||||
- [ ] schedules/YouTubeEditForm.jsx
|
||||
- [ ] categories/List.jsx
|
||||
- [ ] bots/Manager.jsx
|
||||
- [ ] dict/Manager.jsx
|
||||
|
||||
### 기타
|
||||
- [ ] App.jsx (BrowserView/MobileView 라우팅)
|
||||
- [ ] main.jsx
|
||||
|
||||
### CSS 파일
|
||||
- [x] index.css
|
||||
- [ ] mobile.css (모바일 전용 스타일, 달력 등)
|
||||
- [ ] pc.css (PC 전용 스타일)
|
||||
|
||||
### 기타 파일
|
||||
- [ ] data/dummy.js (개발용 더미 데이터)
|
||||
- [ ] .env (VITE_KAKAO_JS_KEY - 콘서트 장소 검색용, 미구현)
|
||||
- [ ] public/favicon.ico
|
||||
|
||||
## 사용 라이브러리 (package.json)
|
||||
|
||||
| 라이브러리 | 용도 | 사용 위치 |
|
||||
|-----------|------|----------|
|
||||
| react-device-detect | PC/Mobile 분기 | App.jsx |
|
||||
| @tanstack/react-query | 데이터 패칭 | 모든 페이지 |
|
||||
| @tanstack/react-virtual | 가상화 리스트 | 관리자 일정 목록 |
|
||||
| react-calendar | 캘린더 | 일정 페이지 |
|
||||
| react-colorful | 색상 선택 | 카테고리 관리 |
|
||||
| react-photo-album | 앨범 갤러리 | 앨범 갤러리 |
|
||||
| react-infinite-scroll-component | 무한 스크롤 | 일정 검색 |
|
||||
| react-intersection-observer | 뷰포트 감지 | 애니메이션 |
|
||||
| react-ios-time-picker | 시간 선택 | 일정 폼 |
|
||||
| react-linkify | URL 자동 링크 | 일정 상세 |
|
||||
| react-window | 가상화 리스트 | 긴 목록 |
|
||||
| swiper | 슬라이더 | 앨범 상세 |
|
||||
| canvas-confetti | 축하 효과 | 생일 페이지 |
|
||||
| framer-motion | 애니메이션 | 전체 |
|
||||
| dayjs | 날짜 처리 | 전체 |
|
||||
| zustand | 상태 관리 | 전체 |
|
||||
|
||||
## 특수 패턴 및 주의사항
|
||||
|
||||
### React Query 고급 사용
|
||||
- `useInfiniteQuery` - 일정 검색 무한 스크롤 (mobile/Schedule)
|
||||
- `useQuery` - 일반 데이터 패칭
|
||||
|
||||
### Swiper 사용 시
|
||||
```jsx
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Virtual } from 'swiper/modules';
|
||||
import 'swiper/css';
|
||||
```
|
||||
- 사용 위치: mobile/Members, mobile/AlbumDetail, mobile/AlbumGallery
|
||||
|
||||
### 가상화 리스트
|
||||
- `useVirtualizer` from `@tanstack/react-virtual`
|
||||
- 사용 위치: mobile/Schedule, admin/AdminSchedule
|
||||
|
||||
### 교차 관찰자
|
||||
- `useInView` from `react-intersection-observer`
|
||||
- 사용 위치: mobile/Schedule (무한 스크롤 트리거)
|
||||
|
||||
### 축하 효과
|
||||
- `canvas-confetti` - 생일 페이지 폭죽 효과
|
||||
- 사용 위치: pc/Birthday, mobile/Schedule (생일 일정)
|
||||
|
||||
### 색상 선택기
|
||||
- `HexColorPicker` from `react-colorful`
|
||||
- 사용 위치: admin/AdminScheduleCategory
|
||||
|
||||
### 앨범 갤러리
|
||||
- `RowsPhotoAlbum` from `react-photo-album`
|
||||
- 사용 위치: pc/AlbumGallery, mobile/AlbumGallery
|
||||
|
||||
### Kakao API (미구현)
|
||||
- 콘서트 장소 검색: `/api/admin/kakao/places`
|
||||
- 환경변수: `VITE_KAKAO_JS_KEY`
|
||||
- 사용 예정: 콘서트 일정 추가 시 장소 검색
|
||||
|
||||
## 마이그레이션 진행 상황
|
||||
|
||||
### 완료된 작업 (재검토 필요)
|
||||
- [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)
|
||||
|
||||
### 미완료 작업
|
||||
- [ ] 관리자 API 전체
|
||||
- [ ] 관리자 컴포넌트 전체
|
||||
- [ ] 관리자 페이지 전체
|
||||
- [ ] 누락된 공통 컴포넌트 (LightboxIndicator, Tooltip, ScrollToTop)
|
||||
- [ ] 누락된 페이지 (AlbumDetail, AlbumGallery, TrackDetail, ScheduleDetail, Birthday)
|
||||
- [ ] 누락된 훅 (useToast)
|
||||
- [ ] PC/Mobile 페이지 분리
|
||||
|
||||
### 최종 검증
|
||||
- [ ] 모든 라우트 동작 확인
|
||||
- [ ] PC/Mobile 전환 테스트
|
||||
- [ ] 관리자 기능 테스트
|
||||
- [ ] frontend-temp → frontend 교체
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue