import { useState, useEffect, useRef } from 'react';
import { useNavigate, useParams, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
Save, Home, ChevronRight, LogOut, Music, Trash2, Plus, Image, Star,
ChevronDown
} from 'lucide-react';
import Toast from '../../../components/Toast';
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
import useToast from '../../../hooks/useToast';
// 커스텀 드롭다운 컴포넌트
function CustomSelect({ value, onChange, options, placeholder }) {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handleClickOutside = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
{isOpen && (
{options.map((option) => (
))}
)}
);
}
function AdminAlbumForm() {
const navigate = useNavigate();
const { id } = useParams();
const isEditMode = !!id;
const coverInputRef = useRef(null);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [coverPreview, setCoverPreview] = useState(null);
const [coverFile, setCoverFile] = useState(null);
const { toast, setToast } = useToast();
const [formData, setFormData] = useState({
title: '',
album_type: '',
album_type_short: '',
release_date: '',
cover_original_url: '',
cover_medium_url: '',
cover_thumb_url: '',
folder_name: '',
description: '',
});
const [tracks, setTracks] = useState([]);
useEffect(() => {
const token = localStorage.getItem('adminToken');
const userData = localStorage.getItem('adminUser');
if (!token || !userData) {
navigate('/admin');
return;
}
setUser(JSON.parse(userData));
if (isEditMode) {
setLoading(true);
fetch(`/api/albums/${id}`)
.then(res => res.json())
.then(data => {
setFormData({
title: data.title || '',
album_type: data.album_type || '',
album_type_short: data.album_type_short || '',
release_date: data.release_date ? data.release_date.split('T')[0] : '',
cover_original_url: data.cover_original_url || '',
cover_medium_url: data.cover_medium_url || '',
cover_thumb_url: data.cover_thumb_url || '',
folder_name: data.folder_name || '',
description: data.description || '',
});
if (data.cover_medium_url || data.cover_original_url) {
setCoverPreview(data.cover_medium_url || data.cover_original_url);
}
setTracks(data.tracks || []);
setLoading(false);
})
.catch(error => {
console.error('앨범 로드 오류:', error);
setLoading(false);
});
}
}, [id, isEditMode, navigate]);
const handleLogout = () => {
localStorage.removeItem('adminToken');
localStorage.removeItem('adminUser');
navigate('/admin');
};
const handleInputChange = (e) => {
const { name, value } = e.target;
// 앨범명 변경 시 RustFS 폴더명 자동 생성
if (name === 'title') {
const folderName = value
.toLowerCase()
.replace(/[\s.]+/g, '-') // 띄어쓰기, 점을 하이픈으로
.replace(/[^a-z0-9가-힣-]/g, '') // 특수문자 제거 (영문, 숫자, 한글, 하이픈만 유지)
.replace(/-+/g, '-') // 연속 하이픈 하나로
.replace(/^-|-$/g, ''); // 앞뒤 하이픈 제거
setFormData(prev => ({ ...prev, title: value, folder_name: folderName }));
} else {
setFormData(prev => ({ ...prev, [name]: value }));
}
};
const handleCoverChange = (e) => {
const file = e.target.files[0];
if (file) {
setCoverFile(file);
const reader = new FileReader();
reader.onloadend = () => {
setCoverPreview(reader.result);
};
reader.readAsDataURL(file);
}
};
const addTrack = () => {
setTracks(prev => [...prev, {
track_number: prev.length + 1,
title: '',
is_title_track: false,
duration: '',
}]);
};
const removeTrack = (index) => {
setTracks(prev => prev.filter((_, i) => i !== index).map((track, i) => ({
...track,
track_number: i + 1
})));
};
const updateTrack = (index, field, value) => {
// 작사/작곡/편곡 필드에서 '|' (전각 세로 막대)를 ', '로 자동 변환
let processedValue = value;
if (['lyricist', 'composer', 'arranger'].includes(field)) {
processedValue = value.replace(/[||]/g, ', ');
}
setTracks(prev => prev.map((track, i) =>
i === index ? { ...track, [field]: processedValue } : track
));
};
const handleSubmit = async (e) => {
e.preventDefault();
// 커스텀 검증
if (!formData.title.trim()) {
setToast({ message: '앨범명을 입력해주세요.', type: 'warning' });
return;
}
if (!formData.folder_name.trim()) {
setToast({ message: 'RustFS 폴더명을 입력해주세요.', type: 'warning' });
return;
}
if (!formData.album_type_short) {
setToast({ message: '앨범 타입을 선택해주세요.', type: 'warning' });
return;
}
if (!formData.release_date) {
setToast({ message: '발매일을 선택해주세요.', type: 'warning' });
return;
}
if (!formData.album_type.trim()) {
setToast({ message: '앨범 유형을 입력해주세요.', type: 'warning' });
return;
}
setSaving(true);
try {
const token = localStorage.getItem('adminToken');
const url = isEditMode ? `/api/admin/albums/${id}` : '/api/admin/albums';
const method = isEditMode ? 'PUT' : 'POST';
const submitData = new FormData();
submitData.append('data', JSON.stringify({ ...formData, tracks }));
if (coverFile) {
submitData.append('cover', coverFile);
}
const response = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${token}`,
},
body: submitData,
});
if (!response.ok) {
throw new Error('저장 실패');
}
navigate('/admin/albums');
} catch (error) {
console.error('저장 오류:', error);
setToast({ message: '저장 중 오류가 발생했습니다.', type: 'error' });
} finally {
setSaving(false);
}
};
const albumTypes = ['정규', '미니', '싱글'];
const pageVariants = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 }
};
return (
{/* Toast */}
setToast(null)} />
{/* 헤더 */}
{/* 메인 콘텐츠 */}
{/* 브레드크럼 */}
앨범 관리
{isEditMode ? '앨범 수정' : '새 앨범 추가'}
{/* 타이틀 */}
{isEditMode ? '앨범 수정' : '새 앨범 추가'}
앨범 정보와 트랙을 입력하세요
{loading ? (
) : (
)}
);
}
export default AdminAlbumForm;