ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 17. 탭메뉴 배지 사용하기 - 해당 날짜의 할일 갯수 보여주기
    프로젝트/할일목록 앱 (RN) 2023. 10. 27. 17:53
    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
Designed by Tistory.