import React, { useEffect, useState, useCallback } from 'react'; import { View, Text, FlatList, TextInput, TouchableOpacity, ActivityIndicator, RefreshControl, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { getSchedules, getCategories, Schedule, ScheduleCategory } from '../api/schedules'; import { colors } from '../constants/colors'; export default function ScheduleScreen() { const [schedules, setSchedules] = useState([]); const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [loadingMore, setLoadingMore] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState(null); const [offset, setOffset] = useState(0); const [hasMore, setHasMore] = useState(true); const LIMIT = 20; const fetchCategories = async () => { try { const data = await getCategories(); setCategories(data); } catch (error) { console.error('카테고리 로드 오류:', error); } }; const fetchSchedules = async (isRefresh = false) => { if (loadingMore && !isRefresh) return; try { const currentOffset = isRefresh ? 0 : offset; const { schedules: data, total } = await getSchedules({ q: searchQuery || undefined, category: selectedCategory || undefined, limit: LIMIT, offset: currentOffset, }); if (isRefresh) { setSchedules(data); setOffset(LIMIT); } else { setSchedules(prev => [...prev, ...data]); setOffset(prev => prev + LIMIT); } setHasMore(currentOffset + data.length < total); } catch (error) { console.error('일정 로드 오류:', error); } finally { setLoading(false); setRefreshing(false); setLoadingMore(false); } }; useEffect(() => { fetchCategories(); }, []); useEffect(() => { setLoading(true); setOffset(0); setHasMore(true); fetchSchedules(true); }, [searchQuery, selectedCategory]); const onRefresh = () => { setRefreshing(true); fetchSchedules(true); }; const onEndReached = () => { if (!loadingMore && hasMore) { setLoadingMore(true); fetchSchedules(); } }; // 날짜 포맷 const formatDate = (dateStr: string) => { const date = new Date(dateStr); const month = date.getMonth() + 1; const day = date.getDate(); const weekdays = ['일', '월', '화', '수', '목', '금', '토']; const weekday = weekdays[date.getDay()]; return `${month}/${day} (${weekday})`; }; const renderScheduleItem = ({ item }: { item: Schedule }) => ( {item.category_name} {formatDate(item.date)} {item.title} {item.time && ( {item.time.slice(0, 5)} )} {item.members && item.members.length > 0 && ( {item.members.map(m => ( {m.name} ))} )} ); return ( {/* 헤더 */} 일정 {/* 검색 */} {searchQuery && ( setSearchQuery('')}> )} {/* 카테고리 필터 */} String(item.id)} renderItem={({ item }) => ( setSelectedCategory(item.id)} style={{ paddingHorizontal: 14, paddingVertical: 6, borderRadius: 16, marginRight: 8, backgroundColor: selectedCategory === item.id ? colors.primary : colors.backgroundSecondary, }} > {item.name} )} /> {/* 일정 목록 */} {loading ? ( ) : ( String(item.id)} renderItem={renderScheduleItem} contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 20 }} refreshControl={ } onEndReached={onEndReached} onEndReachedThreshold={0.5} ListEmptyComponent={ 일정이 없습니다. } ListFooterComponent={ loadingMore ? ( ) : null } /> )} ); }