From 1cb430907fec31970adcae2beafd07132d1d1cf4 Mon Sep 17 00:00:00 2001 From: caadiq Date: Fri, 16 Jan 2026 21:53:55 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EB=A9=A4=EB=B2=84=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 연동 임시 제거, 더미 데이터로 대체 - position 필드 제거 - 별명(nicknames) 태그 입력 UI 추가 - DB 변경: members 테이블 position/image_url 제거, image_id 추가 - DB 변경: member_nicknames 테이블 생성 Co-Authored-By: Claude Opus 4.5 --- .../src/pages/pc/admin/AdminMemberEdit.jsx | 238 +++++++++++------- frontend/src/pages/pc/admin/AdminMembers.jsx | 33 +-- 2 files changed, 170 insertions(+), 101 deletions(-) diff --git a/frontend/src/pages/pc/admin/AdminMemberEdit.jsx b/frontend/src/pages/pc/admin/AdminMemberEdit.jsx index 1da9ffe..aa81d59 100644 --- a/frontend/src/pages/pc/admin/AdminMemberEdit.jsx +++ b/frontend/src/pages/pc/admin/AdminMemberEdit.jsx @@ -1,16 +1,27 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect } from 'react'; import { useNavigate, useParams, Link } from 'react-router-dom'; -import { motion, AnimatePresence } from 'framer-motion'; -import { - Save, Upload, - Home, ChevronRight, User, Instagram, Calendar, Briefcase +import { motion } from 'framer-motion'; +import { + Save, Upload, X, + Home, ChevronRight, User, Instagram, Calendar, Tag } from 'lucide-react'; import Toast from '../../../components/Toast'; import CustomDatePicker from '../../../components/admin/CustomDatePicker'; import AdminLayout from '../../../components/admin/AdminLayout'; import useToast from '../../../hooks/useToast'; -import * as authApi from '../../../api/admin/auth'; -import * as membersApi from '../../../api/admin/members'; + +// 임시 더미 데이터 (API 구현 전까지 사용) +const DUMMY_MEMBERS = { + '이새롬': { id: 1, name: '이새롬', name_en: 'SAEROM', birth_date: '1997-01-07', instagram: 'https://www.instagram.com/saeromssee', is_former: true, image_url: null, nicknames: ['새롬', '큰새롬'] }, + '송하영': { id: 2, name: '송하영', name_en: 'HAYOUNG', birth_date: '1997-09-29', instagram: 'https://www.instagram.com/shy9_29/', is_former: false, image_url: null, nicknames: ['하영', '햄', '햐미'] }, + '장규리': { id: 3, name: '장규리', name_en: 'GYURI', birth_date: '1997-12-27', instagram: 'https://www.instagram.com/gyurious_j', is_former: true, image_url: null, nicknames: ['규리', '뀨리'] }, + '박지원': { id: 4, name: '박지원', name_en: 'JIWON', birth_date: '1998-03-20', instagram: 'https://www.instagram.com/xjiwonparkx/', is_former: false, image_url: null, nicknames: ['지원', '쭈니', '지워니'] }, + '노지선': { id: 5, name: '노지선', name_en: 'JISUN', birth_date: '1998-11-23', instagram: 'https://www.instagram.com/rosieline_', is_former: true, image_url: null, nicknames: ['지선'] }, + '이서연': { id: 6, name: '이서연', name_en: 'SEOYEON', birth_date: '2000-01-22', instagram: 'https://www.instagram.com/im_theyeon', is_former: true, image_url: null, nicknames: ['서연', '써연'] }, + '이채영': { id: 7, name: '이채영', name_en: 'CHAEYOUNG', birth_date: '2000-05-14', instagram: 'https://www.instagram.com/chaengrang_/', is_former: false, image_url: null, nicknames: ['채영', '채령'] }, + '이나경': { id: 8, name: '이나경', name_en: 'NAKYUNG', birth_date: '2000-06-01', instagram: 'https://www.instagram.com/blossomlng_0/', is_former: false, image_url: null, nicknames: ['나경', '낭이', '나낭'] }, + '백지헌': { id: 9, name: '백지헌', name_en: 'JIHEON', birth_date: '2003-04-17', instagram: 'https://www.instagram.com/jiheonnibaek/', is_former: false, image_url: null, nicknames: ['지헌', '지허니'] }, +}; function AdminMemberEdit() { const navigate = useNavigate(); @@ -21,44 +32,44 @@ function AdminMemberEdit() { const { toast, setToast } = useToast(); const [imagePreview, setImagePreview] = useState(null); const [imageFile, setImageFile] = useState(null); - + const [nicknameInput, setNicknameInput] = useState(''); + const [formData, setFormData] = useState({ name: '', + name_en: '', birth_date: '', - position: '', instagram: '', - is_former: false + is_former: false, + nicknames: [] }); useEffect(() => { // 로그인 확인 - if (!authApi.hasToken()) { + const token = localStorage.getItem('adminToken'); + const userData = localStorage.getItem('adminUser'); + + if (!token || !userData) { navigate('/admin'); return; } - setUser(authApi.getCurrentUser()); - fetchMember(); - }, [navigate, name]); + setUser(JSON.parse(userData)); - const fetchMember = async () => { - try { - const data = await membersApi.getMember(encodeURIComponent(name)); + // TODO: API 구현 후 fetchMember()로 교체 + const memberData = DUMMY_MEMBERS[decodeURIComponent(name)]; + if (memberData) { setFormData({ - name: data.name || '', - birth_date: data.birth_date ? data.birth_date.split('T')[0] : '', - position: data.position || '', - instagram: data.instagram || '', - is_former: !!data.is_former + name: memberData.name || '', + name_en: memberData.name_en || '', + birth_date: memberData.birth_date || '', + instagram: memberData.instagram || '', + is_former: !!memberData.is_former, + nicknames: memberData.nicknames || [] }); - setImagePreview(data.image_url); - setLoading(false); - } catch (error) { - console.error('멤버 로드 오류:', error); - setToast({ message: '멤버 정보를 불러올 수 없습니다.', type: 'error' }); - setLoading(false); + setImagePreview(memberData.image_url); } - }; + setLoading(false); + }, [navigate, name]); const handleImageChange = (e) => { const file = e.target.files[0]; @@ -70,32 +81,45 @@ function AdminMemberEdit() { } }; + // 별명 추가 + const handleAddNickname = () => { + const trimmed = nicknameInput.trim(); + if (trimmed && !formData.nicknames.includes(trimmed)) { + setFormData({ + ...formData, + nicknames: [...formData.nicknames, trimmed] + }); + setNicknameInput(''); + } + }; + + // 별명 삭제 + const handleRemoveNickname = (nickname) => { + setFormData({ + ...formData, + nicknames: formData.nicknames.filter(n => n !== nickname) + }); + }; + + // Enter 키로 별명 추가 + const handleNicknameKeyDown = (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddNickname(); + } + }; + const handleSubmit = async (e) => { e.preventDefault(); setSaving(true); - try { - const formDataToSend = new FormData(); - - formDataToSend.append('name', formData.name); - formDataToSend.append('birth_date', formData.birth_date); - formDataToSend.append('position', formData.position); - formDataToSend.append('instagram', formData.instagram); - formDataToSend.append('is_former', formData.is_former); - - if (imageFile) { - formDataToSend.append('image', imageFile); - } + // TODO: API 구현 후 실제 저장 로직 추가 + console.log('저장할 데이터:', formData, imageFile); - await membersApi.updateMember(encodeURIComponent(name), formDataToSend); - setToast({ message: '멤버 정보가 수정되었습니다.', type: 'success' }); - setTimeout(() => navigate('/admin/members'), 1000); - } catch (error) { - console.error('수정 오류:', error); - setToast({ message: '수정 중 오류가 발생했습니다.', type: 'error' }); - } finally { + setTimeout(() => { + setToast({ message: '멤버 정보가 수정되었습니다. (API 미구현)', type: 'success' }); setSaving(false); - } + }, 500); }; return ( @@ -141,15 +165,15 @@ function AdminMemberEdit() { -
document.getElementById('imageInput').click()} > {imagePreview ? ( <> - 프로필 미리보기
@@ -178,19 +202,33 @@ function AdminMemberEdit() { {/* 입력 폼 영역 */}
{/* 이름 */} -
- - setFormData({ ...formData, name: e.target.value })} - required - className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" - placeholder="멤버 이름" - /> +
+
+ + setFormData({ ...formData, name: e.target.value })} + required + className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" + placeholder="멤버 이름" + /> +
+
+ + setFormData({ ...formData, name_en: e.target.value })} + className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" + placeholder="ENGLISH NAME" + /> +
{/* 생년월일 */} @@ -205,21 +243,6 @@ function AdminMemberEdit() { />
- {/* 포지션 */} -
- - setFormData({ ...formData, position: e.target.value })} - className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" - placeholder="메인보컬, 리드댄서 등" - /> -
- {/* 인스타그램 */}
+ {/* 별명 */} +
+ +
+ {/* 별명 입력 */} + setNicknameInput(e.target.value)} + onKeyDown={handleNicknameKeyDown} + className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" + placeholder="별명을 입력하고 Enter" + /> + + {/* 별명 태그 목록 */} + {formData.nicknames.length > 0 && ( +
+ {formData.nicknames.map((nickname, index) => ( + + {nickname} + + + ))} +
+ )} +
+

+ 별명은 일정 검색 시 사용됩니다 +

+
+ {/* 활동 상태 */}