diff --git a/docs/frontend-improvement.md b/docs/frontend-improvement.md index 710c7ea..ecbcd49 100644 --- a/docs/frontend-improvement.md +++ b/docs/frontend-improvement.md @@ -42,13 +42,13 @@ components/ - `Schedules.jsx`에 유틸 함수들이 로컬로 재정의됨 ### 1.4 대형 파일 -| 파일 | 라인 수 | 문제 | -|------|---------|------| -| AlbumPhotos.jsx | 1536 | 업로드, 관리, 일괄편집이 한 파일에 | -| Schedules.jsx | 1471 | 중복 유틸 함수, 컴포넌트 미분리 | -| ScheduleForm.jsx | 1046 | 폼 로직과 UI가 섞여있음 | -| ScheduleDict.jsx | 714 | 테이블과 모달이 한 파일에 | -| AlbumForm.jsx | 631 | 트랙/티저 관리가 인라인 | +| 파일 | 원래 라인 수 | 현재 라인 수 | 상태 | +|------|-------------|-------------|------| +| AlbumPhotos.jsx | 1536 | 1536 | 미분리 | +| Schedules.jsx | 1465 | 1159 | ✅ 분리 완료 | +| ScheduleForm.jsx | 1047 | 765 | ✅ 분리 완료 | +| ScheduleDict.jsx | 714 | 714 | 미분리 | +| AlbumForm.jsx | 631 | 631 | 미분리 | --- @@ -90,7 +90,13 @@ components/ │ └── admin/ │ ├── layout/ # Layout, Header │ ├── common/ # ConfirmDialog, DatePicker 등 -│ ├── schedule/ # CategorySelector 등 +│ ├── schedule/ # ✅ 추가된 컴포넌트들: +│ │ ├── AdminScheduleCard.jsx +│ │ ├── CategorySelector.jsx +│ │ ├── ScheduleItem.jsx # 일정 아이템 (리스트용) +│ │ ├── LocationSearchDialog.jsx # 장소 검색 모달 +│ │ ├── MemberSelector.jsx # 멤버 선택 UI +│ │ └── ImageUploader.jsx # 이미지 업로드 │ └── album/ # PhotoUploader 등 ``` @@ -162,12 +168,17 @@ pages/pc/admin/schedules/ - 페이지 stagger 애니메이션 - 통계 카드 AnimatedNumber -### Phase 2: 대형 파일 분리 -1. [ ] Schedules.jsx 분리 -2. [ ] ScheduleForm.jsx 분리 -3. [ ] AlbumPhotos.jsx 분리 -4. [ ] ScheduleDict.jsx 분리 -5. [ ] AlbumForm.jsx 분리 +### Phase 2: 대형 파일 분리 (진행 중) +1. [x] Schedules.jsx 분리 (1465줄 → 1159줄, 306줄 감소) + - `ScheduleItem.jsx` 컴포넌트 추출 + - 검색 모드와 일반 모드에서 공통 사용 +2. [x] ScheduleForm.jsx 분리 (1047줄 → 765줄, 282줄 감소) + - `LocationSearchDialog.jsx` 추출 (장소 검색 모달) + - `MemberSelector.jsx` 추출 (멤버 선택 UI) + - `ImageUploader.jsx` 추출 (이미지 업로드 및 드래그앤드롭) +3. [ ] AlbumPhotos.jsx 분리 (1536줄, 구조 복잡) +4. [ ] ScheduleDict.jsx 분리 (714줄) +5. [ ] AlbumForm.jsx 분리 (631줄) ### Phase 3: 추가 개선 1. [x] 관리자 페이지용 에러 페이지 추가 (404) diff --git a/frontend-temp/src/components/pc/admin/schedule/ImageUploader.jsx b/frontend-temp/src/components/pc/admin/schedule/ImageUploader.jsx new file mode 100644 index 0000000..43ffd13 --- /dev/null +++ b/frontend-temp/src/components/pc/admin/schedule/ImageUploader.jsx @@ -0,0 +1,141 @@ +/** + * 이미지 업로드 컴포넌트 + * - 다중 이미지 업로드 및 드래그 앤 드롭 정렬 + */ +import { useState, memo } from 'react'; +import { Image, Plus, X } from 'lucide-react'; + +/** + * @param {Object} props + * @param {Array} props.previews - 이미지 미리보기 URL 배열 + * @param {Function} props.onUpload - 파일 업로드 핸들러 (files) + * @param {Function} props.onDelete - 이미지 삭제 핸들러 (index) + * @param {Function} props.onReorder - 이미지 순서 변경 핸들러 (fromIndex, toIndex) + * @param {Function} props.onOpenLightbox - 라이트박스 열기 핸들러 (index) + */ +const ImageUploader = memo(function ImageUploader({ + previews, + onUpload, + onDelete, + onReorder, + onOpenLightbox, +}) { + const [draggedIndex, setDraggedIndex] = useState(null); + const [dragOverIndex, setDragOverIndex] = useState(null); + + // 파일 선택 + const handleFileChange = (e) => { + const files = Array.from(e.target.files); + if (files.length > 0) { + onUpload(files); + } + // input 초기화 (같은 파일 다시 선택 가능하도록) + e.target.value = ''; + }; + + // 드래그 시작 + const handleDragStart = (e, index) => { + setDraggedIndex(index); + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/plain', index); + }; + + // 드래그 오버 + const handleDragOver = (e, index) => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + if (dragOverIndex !== index) { + setDragOverIndex(index); + } + }; + + // 드래그 종료 + const handleDragEnd = () => { + setDraggedIndex(null); + setDragOverIndex(null); + }; + + // 드롭 - 이미지 순서 변경 + const handleDrop = (e, dropIndex) => { + e.preventDefault(); + if (draggedIndex === null || draggedIndex === dropIndex) { + handleDragEnd(); + return; + } + onReorder(draggedIndex, dropIndex); + handleDragEnd(); + }; + + return ( +
검색어를 입력하고 검색 버튼을 눌러주세요
+장소명을 입력하고 검색해주세요
+검색어를 입력하고 검색 버튼을 눌러주세요
-장소명을 입력하고 검색해주세요
-