import React, { useEffect, useState, useRef } from 'react'; import { View, Text, ScrollView, Image, TouchableOpacity, ActivityIndicator, RefreshControl, StyleSheet, Animated, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { ChevronRight, Clock, Tag } from 'lucide-react-native'; import { getAlbums, Album } from '../api/albums'; import { getMembers, Member } from '../api/members'; import { getUpcomingSchedules, Schedule } from '../api/schedules'; import { colors } from '../constants/colors'; import Header from '../components/common/Header'; import type { AlbumStackParamList } from '../navigation/AppNavigator'; type NavigationProp = NativeStackNavigationProp; // 전체 배경색 (tailwind bg-gray-50) const BG_GRAY = '#F9FAFB'; export default function HomeScreen() { const navigation = useNavigation(); const [members, setMembers] = useState([]); const [albums, setAlbums] = useState([]); const [schedules, setSchedules] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); // 애니메이션 const fadeAnim = useRef(new Animated.Value(0)).current; const slideAnim = useRef(new Animated.Value(20)).current; const fetchData = async () => { try { const [membersData, albumsData, schedulesData] = await Promise.all([ getMembers(), getAlbums(), getUpcomingSchedules(3), ]); setMembers(membersData.filter(m => !m.is_former)); setAlbums(albumsData.slice(0, 2)); setSchedules(schedulesData || []); // 애니메이션 시작 Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true, }), Animated.timing(slideAnim, { toValue: 0, duration: 500, useNativeDriver: true, }), ]).start(); } catch (error) { console.error('데이터 로드 오류:', error); } finally { setLoading(false); setRefreshing(false); } }; useEffect(() => { fetchData(); }, []); const onRefresh = () => { setRefreshing(true); fadeAnim.setValue(0); slideAnim.setValue(20); fetchData(); }; // 날짜 포맷 const formatScheduleDate = (dateStr: string) => { const date = new Date(dateStr); const today = new Date(); const currentYear = today.getFullYear(); const currentMonth = today.getMonth(); const scheduleYear = date.getFullYear(); const scheduleMonth = date.getMonth(); const isCurrentYear = scheduleYear === currentYear; const isCurrentMonth = isCurrentYear && scheduleMonth === currentMonth; const weekdays = ['일', '월', '화', '수', '목', '금', '토']; return { day: date.getDate(), weekday: weekdays[date.getDay()], year: scheduleYear, month: scheduleMonth + 1, isCurrentYear, isCurrentMonth, }; }; if (loading) { return (
); } return ( {/* 공통 헤더 */}
} showsVerticalScrollIndicator={false} contentContainerStyle={styles.scrollContent} > {/* 히어로 섹션 - bg-gradient-to-br from-primary to-primary-dark py-12 px-4 */} fromis_9 프로미스나인 인사드리겠습니다. 둘, 셋!{'\n'} 이제는 약속해 소중히 간직해,{'\n'} 당신의 아이돌로 성장하겠습니다! {/* 장식 원 */} {/* 멤버 섹션 */} 멤버 전체보기 {members.slice(0, 5).map((member) => ( {member.image_url && ( )} {member.name} ))} {/* 앨범 섹션 */} 앨범 navigation.navigate('AlbumList')} > 전체보기 {albums.map((album) => ( navigation.navigate('AlbumDetail', { name: album.folder_name })} activeOpacity={0.98} > {album.title} {album.release_date?.slice(0, 4)} ))} {/* 일정 섹션 */} 다가오는 일정 전체보기 {schedules.length > 0 ? ( {schedules.map((schedule) => { const dateInfo = formatScheduleDate(schedule.date); const memberList = schedule.member_names ? schedule.member_names.split(',').map(n => n.trim()).filter(Boolean) : []; return ( {/* 날짜 영역 */} {!dateInfo.isCurrentYear && ( {dateInfo.year}.{dateInfo.month} )} {dateInfo.isCurrentYear && !dateInfo.isCurrentMonth && ( {dateInfo.month}월 )} {dateInfo.day} {dateInfo.weekday} {/* 세로 구분선 */} {/* 내용 영역 */} {schedule.title} {schedule.time && ( {schedule.time.slice(0, 5)} )} {schedule.category_name && ( {schedule.category_name} )} {memberList.length > 0 && ( {(memberList.length >= 5 ? ['프로미스나인'] : memberList).map((name, i) => ( {name} ))} )} ); })} ) : ( 다가오는 일정이 없습니다 )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: BG_GRAY, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: BG_GRAY, }, // 헤더 header: { backgroundColor: '#FFFFFF', paddingVertical: 14, alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 2, elevation: 2, }, headerTitle: { fontSize: 20, fontWeight: 'bold', color: colors.primary, }, scrollView: { flex: 1, }, scrollContent: { paddingBottom: 0, }, // 히어로 섹션 heroSection: { backgroundColor: colors.primary, paddingVertical: 48, paddingHorizontal: 16, position: 'relative', overflow: 'hidden', }, heroOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.1)', }, heroContent: { alignItems: 'center', }, heroTitle: { fontSize: 30, fontWeight: 'bold', color: '#FFFFFF', marginBottom: 4, }, heroSubtitle: { fontSize: 18, color: '#FFFFFF', fontWeight: '300', marginBottom: 12, }, heroText: { fontSize: 14, color: 'rgba(255,255,255,0.8)', textAlign: 'center', lineHeight: 22, }, decorCircle: { position: 'absolute', borderRadius: 999, backgroundColor: 'rgba(255,255,255,0.1)', }, decorCircle1: { width: 128, height: 128, right: -32, top: -32, }, decorCircle2: { width: 96, height: 96, left: -24, bottom: -24, backgroundColor: 'rgba(255,255,255,0.05)', }, // 섹션 공통 section: { paddingHorizontal: 16, paddingVertical: 24, }, scheduleSection: { paddingHorizontal: 16, paddingTop: 16, paddingBottom: 16, }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#111827', }, moreButton: { flexDirection: 'row', alignItems: 'center', gap: 4, }, moreButtonText: { fontSize: 14, color: colors.primary, }, // 멤버 섹션 membersGrid: { flexDirection: 'row', justifyContent: 'space-between', }, memberItem: { alignItems: 'center', width: '18%', }, memberImageContainer: { width: 56, height: 56, borderRadius: 28, overflow: 'hidden', backgroundColor: '#E5E7EB', marginBottom: 4, }, memberImage: { width: '100%', height: '100%', }, memberName: { fontSize: 12, fontWeight: '500', color: '#111827', textAlign: 'center', }, // 앨범 섹션 albumsGrid: { flexDirection: 'row', gap: 12, }, albumCard: { flex: 1, backgroundColor: '#FFFFFF', borderRadius: 12, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 3, elevation: 2, }, albumImageContainer: { aspectRatio: 1, backgroundColor: '#E5E7EB', }, albumImage: { width: '100%', height: '100%', }, albumInfo: { padding: 12, }, albumTitle: { fontSize: 14, fontWeight: '500', color: '#111827', }, albumYear: { fontSize: 12, color: '#9CA3AF', marginTop: 2, }, // 일정 섹션 schedulesList: { gap: 12, }, scheduleCard: { flexDirection: 'row', backgroundColor: '#FFFFFF', padding: 16, borderRadius: 12, borderWidth: 1, borderColor: '#F3F4F6', shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.03, shadowRadius: 2, elevation: 1, }, scheduleDateContainer: { alignItems: 'center', justifyContent: 'center', minWidth: 50, }, scheduleDateExtra: { fontSize: 10, color: '#9CA3AF', fontWeight: '500', }, scheduleDay: { fontSize: 24, fontWeight: 'bold', color: colors.primary, }, scheduleWeekday: { fontSize: 12, color: '#9CA3AF', fontWeight: '500', }, scheduleDivider: { width: 1, backgroundColor: '#F3F4F6', marginHorizontal: 16, }, scheduleContent: { flex: 1, }, scheduleTitle: { fontSize: 14, fontWeight: '600', color: '#1F2937', lineHeight: 20, }, scheduleMeta: { flexDirection: 'row', gap: 12, marginTop: 8, }, scheduleMetaItem: { flexDirection: 'row', alignItems: 'center', gap: 4, }, scheduleMetaText: { fontSize: 12, color: '#9CA3AF', }, memberTags: { flexDirection: 'row', flexWrap: 'wrap', gap: 4, marginTop: 8, }, memberTag: { backgroundColor: colors.primary + '1A', paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, }, memberTagText: { fontSize: 10, color: colors.primary, fontWeight: '500', }, emptySchedule: { paddingVertical: 32, alignItems: 'center', }, emptyText: { color: '#9CA3AF', }, });