2026-01-01 09:32:38 +09:00
|
|
|
import { useState, useEffect } from 'react';
|
2026-01-01 10:20:54 +09:00
|
|
|
import { useNavigate } from 'react-router-dom';
|
2025-12-31 21:51:23 +09:00
|
|
|
import { motion } from 'framer-motion';
|
|
|
|
|
import { Calendar, Music } from 'lucide-react';
|
|
|
|
|
|
|
|
|
|
function Discography() {
|
2026-01-01 10:20:54 +09:00
|
|
|
const navigate = useNavigate();
|
2026-01-01 09:32:38 +09:00
|
|
|
const [albums, setAlbums] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetch('/api/albums')
|
|
|
|
|
.then(res => res.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
setAlbums(data);
|
|
|
|
|
setLoading(false);
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('앨범 데이터 로드 오류:', error);
|
|
|
|
|
setLoading(false);
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 날짜 포맷팅
|
|
|
|
|
const formatDate = (dateStr) => {
|
|
|
|
|
if (!dateStr) return '';
|
|
|
|
|
const date = new Date(dateStr);
|
|
|
|
|
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 타이틀곡 찾기
|
|
|
|
|
const getTitleTrack = (tracks) => {
|
|
|
|
|
if (!tracks || tracks.length === 0) return '';
|
|
|
|
|
const titleTrack = tracks.find(t => t.is_title_track);
|
|
|
|
|
return titleTrack ? titleTrack.title : tracks[0].title;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 앨범 타입별 개수 계산
|
|
|
|
|
const albumStats = {
|
|
|
|
|
정규: albums.filter(a => a.album_type === '정규').length,
|
|
|
|
|
미니: albums.filter(a => a.album_type === '미니').length,
|
|
|
|
|
싱글: albums.filter(a => a.album_type === '싱글').length,
|
|
|
|
|
총: albums.length
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-01 10:20:54 +09:00
|
|
|
// 앨범 클릭 핸들러
|
|
|
|
|
const handleAlbumClick = (albumId) => {
|
|
|
|
|
navigate(`/discography/${albumId}`);
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-01 09:32:38 +09:00
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="py-16 flex justify-center items-center min-h-[60vh]">
|
|
|
|
|
<div className="animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent"></div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 21:51:23 +09:00
|
|
|
return (
|
|
|
|
|
<div className="py-16">
|
|
|
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
|
|
|
{/* 헤더 */}
|
2026-01-01 10:20:54 +09:00
|
|
|
<div className="text-center mb-8">
|
2025-12-31 21:51:23 +09:00
|
|
|
<motion.h1
|
|
|
|
|
initial={{ opacity: 0, y: -20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
className="text-4xl font-bold mb-4"
|
|
|
|
|
>
|
2026-01-01 10:20:54 +09:00
|
|
|
앨범
|
2025-12-31 21:51:23 +09:00
|
|
|
</motion.h1>
|
|
|
|
|
<motion.p
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
transition={{ delay: 0.2 }}
|
|
|
|
|
className="text-gray-500"
|
|
|
|
|
>
|
|
|
|
|
프로미스나인의 음악을 만나보세요
|
|
|
|
|
</motion.p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-01 10:20:54 +09:00
|
|
|
{/* 통계 - 상단 배치 */}
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: 0.3 }}
|
|
|
|
|
className="mb-12 grid grid-cols-4 gap-4"
|
|
|
|
|
>
|
|
|
|
|
<div className="bg-gray-50 rounded-xl p-4 text-center">
|
|
|
|
|
<p className="text-2xl font-bold text-primary mb-1">{albumStats.정규}</p>
|
|
|
|
|
<p className="text-gray-500 text-sm">정규 앨범</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="bg-gray-50 rounded-xl p-4 text-center">
|
|
|
|
|
<p className="text-2xl font-bold text-primary mb-1">{albumStats.미니}</p>
|
|
|
|
|
<p className="text-gray-500 text-sm">미니 앨범</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="bg-gray-50 rounded-xl p-4 text-center">
|
|
|
|
|
<p className="text-2xl font-bold text-primary mb-1">{albumStats.싱글}</p>
|
|
|
|
|
<p className="text-gray-500 text-sm">싱글 앨범</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="bg-gray-50 rounded-xl p-4 text-center">
|
|
|
|
|
<p className="text-2xl font-bold text-primary mb-1">{albumStats.총}</p>
|
|
|
|
|
<p className="text-gray-500 text-sm">총 앨범</p>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
2025-12-31 21:51:23 +09:00
|
|
|
{/* 앨범 그리드 */}
|
|
|
|
|
<div className="grid grid-cols-4 gap-8">
|
|
|
|
|
{albums.map((album, index) => (
|
|
|
|
|
<motion.div
|
|
|
|
|
key={album.id}
|
|
|
|
|
initial={{ opacity: 0, y: 30 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: index * 0.1 }}
|
2026-01-01 10:20:54 +09:00
|
|
|
className="group bg-white rounded-2xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 cursor-pointer"
|
|
|
|
|
onClick={() => handleAlbumClick(album.id)}
|
2025-12-31 21:51:23 +09:00
|
|
|
>
|
|
|
|
|
{/* 앨범 커버 */}
|
2026-01-01 10:20:54 +09:00
|
|
|
<div
|
|
|
|
|
className="relative aspect-square bg-gray-100 overflow-hidden"
|
|
|
|
|
style={{ viewTransitionName: `album-cover-${album.id}` }}
|
|
|
|
|
>
|
2025-12-31 21:51:23 +09:00
|
|
|
<img
|
2026-01-01 09:32:38 +09:00
|
|
|
src={album.cover_url}
|
2025-12-31 21:51:23 +09:00
|
|
|
alt={album.title}
|
|
|
|
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* 호버 오버레이 */}
|
|
|
|
|
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
|
|
|
|
<div className="text-center text-white">
|
|
|
|
|
<Music size={40} className="mx-auto mb-2" />
|
2026-01-01 09:32:38 +09:00
|
|
|
<p className="text-sm">{album.tracks?.length || 0}곡 수록</p>
|
2025-12-31 21:51:23 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 앨범 정보 */}
|
|
|
|
|
<div className="p-6">
|
2026-01-01 10:20:54 +09:00
|
|
|
<div className="flex items-center gap-2 mb-1">
|
|
|
|
|
<h3 className="font-bold text-lg truncate flex-1">{album.title}</h3>
|
|
|
|
|
<span className="px-2 py-0.5 bg-primary/10 text-primary text-xs font-medium rounded-full flex-shrink-0">
|
2026-01-01 14:15:39 +09:00
|
|
|
{album.album_type_short || album.album_type}
|
2026-01-01 10:20:54 +09:00
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-12-31 21:51:23 +09:00
|
|
|
<p className="text-primary text-sm font-medium mb-3">
|
2026-01-01 09:32:38 +09:00
|
|
|
{getTitleTrack(album.tracks)}
|
2025-12-31 21:51:23 +09:00
|
|
|
</p>
|
|
|
|
|
<div className="flex items-center gap-2 text-sm text-gray-500">
|
|
|
|
|
<Calendar size={14} />
|
2026-01-01 09:32:38 +09:00
|
|
|
<span>{formatDate(album.release_date)}</span>
|
2025-12-31 21:51:23 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Discography;
|