728x90
* 특정날짜의 할일목록만 추출하는 기능을 함수로 정의하기
export const getTodosToday = (date, todos) => {
const today = getToday(date) // 시간제외
const tomorrow = getTomorrow(getToday(date))
const todosToday = todos.filter(todo => todo.createdAt?.toDate() >= today && todo.createdAt?.toDate() < tomorrow)
return {todosToday, today}
}
utils > time.js 파일에 해당 코드를 추가한다.
import React, { useState, useEffect, useRef } from 'react' // 카테고리 저장을 위한 useRef 임포트(수정)
import {
addData,
removeData,
getCurrentTime,
// getCollection // 주석처리
} from '../apis/firebase'
import { // 오늘과 내일 날짜기준을 계산하는 유틸리티 함수
getToday,
getTomorrow,
getTodosToday
} from '../utils/time'
import {
SafeAreaView,
View, Text,
StyleSheet,
StatusBar,
Keyboard,
FlatList,
TouchableHighlight,
Modal, Pressable
} from 'react-native'
import DateHeader from '../components/DateHeader'
import Default from '../components/Default'
import TodoInsert from '../components/TodoInsert'
import TodoList from '../components/TodoList'
import DropdownList from '../components/DropdownList'
function HomeScreen({ navigation, caretType, setCaretType, todos, loading, route, setNumOfTodosToday }){ // 필요한 데이터 추가 (todos, loading, route)
const categories = ['자기계발', '업무', '오락', '여행', '연애', 'IT', '취미']
const [todoText, setTodoText] = useState('')
const [warning, setWarning] = useState(false)
const [modalOpen, setModalOpen] = useState(false)
const [todoToRemove, setTodoToRemove] = useState({id: null, title: ''})
// 오늘/내일의 날짜를 기준으로 할일목록을 필터링하고 정렬함
const category = useRef('') // 카테고리 변수
const date = (route.params && route.params.date) ? new Date(route.params.date) : new Date()
const {todosToday, today} = getTodosToday(date, todos)
const todosTodayLatest = [...todosToday] // 원본복사
todosTodayLatest.sort((a, b) => b.createdAt.seconds - a.createdAt.seconds) // 최신순 정렬
console.log("현재 선택날짜: ", date)
console.log("날짜비교: ", date.getTime(), today.getTime() != getToday(new Date()).getTime())
const onInsertTodo = async (trimedText) => {
if(!category.current){ // 카테고리를 선택하지 않은 경우
setTodoText('카테고리를 먼저 선택해주세요!')
setWarning(true)
return
}
if(trimedText && trimedText.length > 3){ // 최소 글자수 제한
if(todos.filter(todo => todo.title === trimedText).length > 0){
setTodoText('중복된 할일입니다.')
setWarning(true)
}else{
const newTodo = {
title: trimedText,
category: category.current || '자기계발', // 선택한 카테고리 설정 (수정)
isDone: false,
createdAt: getCurrentTime(), // 클라이언트 기준이 아니라 서버기준 저장시각
}
await addData('todos', newTodo)
Keyboard.dismiss() // 추가버튼 클릭시 키보드 감추기
setTodoText('') // 입력창 초기화
category.current = '' // 카테고리 초기화 (추가)
}
}else{
console.log('3자 이상 입력하세요!')
setTodoText('3자 이상 입력하세요!')
setWarning(true)
}
}
const closeDropdown = () => {
caretType && setCaretType(false)
}
const selectCategory = (item, e) => { // 카테고리 드롭다운 선택시 (추가)
console.log("카테고리: ", item)
closeDropdown()
category.current = item
}
const handleOutSideOfMenu = (e) => {
console.log('홈화면을 터치하셨습니다.')
closeDropdown()
}
const removeTodo = (id, title) => {
setModalOpen(true)
setTodoToRemove({id, title})
console.log(`할일 [${title}] 제거`)
}
const handleRemove = () => {
setModalOpen(false)
setTodoToRemove({id: null, title: ''})
removeData('todos', todoToRemove.id)
}
useEffect(() => navigation.addListener('focus', () => console.log('페이지 로딩')), [])
useEffect(() => navigation.addListener('blur', () => console.log('페이지 벗어남')), [])
useEffect(() => {
setNumOfTodosToday(todosToday.length)
})
return (
<SafeAreaView
style={styles.block}
onTouchStart={handleOutSideOfMenu}>
<StatusBar backgroundColor="#a8c8ffff"></StatusBar>
<Modal
animationType="fade"
transparent={true}
visible={modalOpen}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
setModalOpen(!modalOpen);
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.guideText}>할일 "{todoToRemove.title}" 을 제거하시겠습니까?</Text>
<View style={styles.alignHorizontal}>
<Pressable
style={[styles.button, styles.buttonClose, styles.remove]}
onPress={handleRemove}>
<Text style={styles.textStyle}>삭제</Text>
</Pressable>
<Pressable
style={[styles.button, styles.buttonClose]}
onPress={() => setModalOpen(false)}>
<Text style={styles.textStyle}>닫기</Text>
</Pressable>
</View>
</View>
</View>
</Modal>
{caretType && <DropdownList categories={categories} selectCategory={selectCategory} top={-15}/>}
<DateHeader date={date}/>
{/* 해당날짜 기준 최신순으로 정렬된 할일목록 */}
{todosTodayLatest.length === 0 ?
<Default/> :
<TodoList todos={todosTodayLatest} removeTodo={removeTodo}
/>}
{/* 필터링된 할일목록의 날짜와 현재 날짜가 동일하지 않은 경우 */}
<TodoInsert
onInsertTodo={onInsertTodo}
todoText={todoText}
setTodoText={setTodoText}
warning={warning}
setWarning={setWarning}
disabled={today.getTime()<getToday(new Date()).getTime()}/>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
block: {
flex: 1,
},
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 22,
},
modalView: {
margin: 50,
backgroundColor: 'white',
borderRadius: 20,
padding: 20,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
alignHorizontal: {
flexDirection: 'row',
justifyContent: 'flex-end'
},
guideText: {
fontWeight: 'bold',
fontSize: 15
},
button: {
width: 70,
height: 40,
borderRadius: 10,
padding: 0,
elevation: 2,
marginTop: 30,
marginRight: 5,
justifyContent: 'center'
},
buttonOpen: {
backgroundColor: '#F194FF',
},
buttonClose: {
backgroundColor: '#a8c8ffff',
},
textStyle: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
remove: {
backgroundColor: 'red'
},
modalText: {
marginBottom: 15,
textAlign: 'center',
},
})
export default HomeScreen
screens > HomeScreen.js 파일을 위와 같이 수정한다.
import { // 오늘과 내일 날짜기준을 계산하는 유틸리티 함수
getToday,
getTomorrow,
getTodosToday // 추가
} from '../utils/time'
time.js 파일에 추가한 함수를 사용하기 위하여 임포트한다.
function HomeScreen({ navigation, caretType, setCaretType, todos, loading, route, setNumOfTodosToday }){ // 필요한 데이터 추가 (setNumOfTodosToday)
// 중략
}
setNumOfTodosToday 함수는 App 컴포넌트로 전달받아서 특정날짜의 할일목록의 갯수를 업데이트한다.
const today = getToday(date) // 시간제외
const tomorrow = getTomorrow(getToday(date))
const todosToday = todos.filter(todo => todo.createdAt?.toDate() >= today && todo.createdAt?.toDate() < tomorrow)
특정날짜의 할일목록을 조회하는 위의 코드는 time.js 파일에 함수로 정의해두었기 때문에 더이상 필요없다. 삭제하고 아래와 같이 작성한다.
const {todosToday, today} = getTodosToday(date, todos)
전체 할일목록에서 특정날짜의 할일목록만 필터링해야 하므로 전체 할일목록과 특정날짜를 인자로 전달한다.
useEffect(() => {
setNumOfTodosToday(todosToday.length)
})
홈 화면이 로딩될때 특정날짜의 할일목록 갯수를 최신값으로 업데이트한다.
import React, { useState, useEffect } from 'react'
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native'
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Icon from 'react-native-vector-icons/MaterialIcons'
import HomeScreen from './screens/HomeScreen';
import CalendarScreen from './screens/CalendarScreen';
import DashBoardSceen from './screens/DashBoardScreen';
import SettingsScreen from './screens/SettingsScreen';
import DropdownCategory from './components/DropdownCategory'
import { // 할일목록 조회를 위한 유틸리티 함수 추가
getCollection,
} from './apis/firebase'
// const Stack = createNativeStackNavigator()
const Tab = createBottomTabNavigator()
export default function App({navigation}) {
const [todos, setTodos] = useState([]) // 할일목록 상태 (HomeScreen -> App 이동)
const [loading, setLoading] = useState(true) // 할일목록 상태 (HomeScreen -> App 이동)
const [caretType, setCaretType] = useState(false)
const [yearCaret, setYearCaret] = useState(false)
const [monthCaret, setMonthCaret] = useState(false)
const [numOfTodosToday, setNumOfTodosToday] = useState(0)
useEffect(() => { // 할일목록 조회 (HomeScreen -> App 이동)
function onResult(querySnapshot){
const list = []
querySnapshot.forEach(doc => {
console.log(doc.data())
list.push({
...doc.data(),
id: doc.id,
})
})
setTodos(list)
setLoading(false)
}
function onError(error){
console.error(`${error} occured when reading todos`)
}
return getCollection('todos',
onResult, onError,
null, null, null)
}, [])
if (loading) {
return (
<View style={styles.block}>
<ActivityIndicator size="large" color="#0047AB"/>
<Text style={styles.loadingText}>Loading ...</Text>
</View>
)
}
return (
<>
<Tab.Navigator initialRouteName = "Home" screenOptions={{
tabBarActiveTintColor: '#a8c8ffff',
// tabBarStyle: {
// backgroundColor: '#333'
// }
}}>
<Tab.Screen name="Home" children={(props) => <HomeScreen {...props} caretType={caretType} setCaretType={setCaretType} todos={todos} loading={loading} setNumOfTodosToday={setNumOfTodosToday}/>} options={{
title: '홈',
tabBarIcon: ({ color, size }) => <Icon name="home" color={color} size={size}/>,
headerTitle: (props) => <DropdownCategory {...props} caretType={caretType} setCaretType={setCaretType} categoryTitle="카테고리"/>,
headerStyle: {
backgroundColor: '#a8c8ffff',
},
headerTitleStyle: {
fontWeight: 'bold',
color: '#fff'
},
tabBarBadge: numOfTodosToday
}}/>
<Tab.Screen name="Calendar" children={(props) => <CalendarScreen
{...props}
yearCaret={yearCaret}
setYearCaret={setYearCaret}
monthCaret={monthCaret}
setMonthCaret={setMonthCaret}/>} options={{
title: '달력',
tabBarIcon: ({ color, size }) => <Icon name="calendar-month" color={color} size={size}/>,
headerTitle: (props) => (<View style={{flexDirection: 'row'}}>
<DropdownCategory {...props} caretType={yearCaret} setCaretType={setYearCaret} categoryTitle="Year"/>
<DropdownCategory {...props} caretType={monthCaret} setCaretType={setMonthCaret} categoryTitle="Month"/>
</View>),
headerStyle: {
backgroundColor: '#a8c8ffff',
},
headerTitleStyle: {
fontWeight: 'bold',
color: '#fff'
},
}}/>
<Tab.Screen name="DashBoard" children={(props) => <DashBoardSceen todos={todos}/>} options={{
title: '통계',
tabBarIcon: ({ color, size }) => <Icon name="dashboard" color={color} size={size}/>
}}/>
<Tab.Screen name="Settings" component={SettingsScreen} options={{
title: '설정',
tabBarIcon: ({ color, size }) => <Icon name="settings" color={color} size={size}/>
}}/>
</Tab.Navigator>
</>
);
}
const styles = StyleSheet.create({
block: {
flex: 1,
backgroundColor: '#a8c8ffff',
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
fontSize: 20,
fontWeight: 'bold',
color: '#fff',
marginTop: 10,
textAlign: 'center'
}
})
App 컴포넌트를 위와 같이 수정한다.
const [numOfTodosToday, setNumOfTodosToday] = useState(0)
특정날짜의 할일목록 갯수를 보여주기 위하여 state 를 정의한다.
children={(props) => <HomeScreen {...props} caretType={caretType} setCaretType={setCaretType} todos={todos} loading={loading} setNumOfTodosToday={setNumOfTodosToday}/>}
HomeScreen 컴포넌트에 setNumOfTodosToday 함수를 사용하기 위하여 전달해준다.
<Tab.Screen name="Home" children={(props) => <HomeScreen {...props} caretType={caretType} setCaretType={setCaretType} todos={todos} loading={loading} setNumOfTodosToday={setNumOfTodosToday}/>} options={{
title: '홈',
tabBarIcon: ({ color, size }) => <Icon name="home" color={color} size={size}/>,
headerTitle: (props) => <DropdownCategory {...props} caretType={caretType} setCaretType={setCaretType} categoryTitle="카테고리"/>,
headerStyle: {
backgroundColor: '#a8c8ffff',
},
headerTitleStyle: {
fontWeight: 'bold',
color: '#fff'
},
tabBarBadge: numOfTodosToday // 추가
}}/>
tabBarBadge: numOfTodosToday 를 추가하면 홈 탭메뉴에 배지가 추가된다.
728x90
'프로젝트 > 할일목록 앱 (RN)' 카테고리의 다른 글
19. 화면 레이아웃 깨지는 문제 해결하기 (2) | 2023.10.31 |
---|---|
18. 소셜로그인 에러 해결하기 (1) | 2023.10.30 |
16. 전체 로그인 로직 수정하기 (0) | 2023.10.27 |
14. 구글폰트 적용하기 - 랜딩페이지 문구 변경 (0) | 2023.10.26 |
15. LoginButton 컴포넌트에 소셜 로그인 기능 구현하기 (0) | 2023.10.26 |