-
17. 탭메뉴 배지 사용하기 - 해당 날짜의 할일 갯수 보여주기프로젝트/할일목록 앱 (RN) 2023. 10. 27. 17:53728x90
* 특정날짜의 할일목록만 추출하는 기능을 함수로 정의하기
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