프로젝트/할일목록 앱 (RN)

20. 미래 날짜에 저장하는 투두가 오늘날짜에 저장되는 에러 해결하기

syleemomo 2023. 11. 6. 14:18
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 파일에 아래 함수를 추가한다. 기존에는 위와 같이 투두 생성시각 기준으로 할일을 필터링했다. 하지만 이렇게 되면 예를 들어 오늘이 11월 6일이고, 11월 15일에 할일을 추가하려고 했으나 11월 15일에 투두가 디스플레이되지 않고, 11월 6일(오늘날짜)에 할일이 추가된다. 왜냐하면 생성시각 기준으로 할일목록을 가져오기 때문이다. 즉, 11월 6일에 생성한 할일은 11월 6일 날짜에만 디스플레이된다. 

export const getTodosBySpecificDate = (date, todos) => {
    const today = getToday(date) // 시간제외
    const tomorrow = getTomorrow(getToday(date))
    const todosToday = todos.filter(todo => todo.dateOfTodo?.toDate() >= today && todo.dateOfTodo?.toDate() < tomorrow)
    return {todosToday, today}
}

투두 데이터 모델에 dateOfTodo 필드를 추가한다. 특정날짜의 할일목록을 보여줄때 필터링 기준을 특정날짜(dateOfTodo) 기준으로 수정한다. 이렇게 하면 생성시각과 관계없이 해당 날짜 기준으로 할일목록을 걸러낸다. 

import React, { useState, useEffect, useRef } from 'react' // 카테고리 저장을 위한 useRef 임포트(수정)
import { 
  addData,
  removeData,
  getCurrentTime,
  // getCollection // 주석처리
} from '../apis/firebase'

import { // 오늘과 내일 날짜기준을 계산하는 유틸리티 함수
  getToday,
  getTomorrow,
  getTodosToday,
  getTodosBySpecificDate
} 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} = getTodosBySpecificDate(date, todos) // dateOfTodo 기준으로 필터링
  const todosTodayLatest = [...todosToday] // 원본복사
  todosTodayLatest.sort((a, b) => b.createdAt?.seconds - a.createdAt?.seconds) // 최신순 정렬 (업데이트되는 시간차 때문에 createdAt 이 null 일수 있음)

  console.log("현재 선택날짜: ", date, todosToday)
  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(), // 클라이언트 기준이 아니라 서버기준 저장시각,
          dateOfTodo: date // createdAt 기준으로 todo 를 필터링하는게 아니라 해당 필드 기준으로 걸러냄
        }
        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,
  getTodosBySpecificDate // 추가
} from '../utils/time'

time.js 파일에 추가한 getTodosBySpecificDate 함수를 임포트한다.

const {todosToday, today} = getTodosToday(date, todos)

기존에는 생성시각 기준으로 할일목록을 조회했지만, 아래와 같이 dateOfTodo 필드 기준으로 할일목록을 필터링하도록 수정한다. 

const {todosToday, today} = getTodosBySpecificDate(date, todos) // dateOfTodo 기준으로 필터링

 

todosTodayLatest.sort((a, b) => b.createdAt.seconds - a.createdAt.seconds) // 최신순 정렬

할일을 추가할때 업데이트되는 시간차 때문에 createdAt 필드가 null 일수 있다. 이를 해결하고자 아래와 같이 코드를 수정하였다. 

todosTodayLatest.sort((a, b) => b.createdAt?.seconds - a.createdAt?.seconds) // 최신순 정렬 (업데이트되는 시간차 때문에 createdAt 이 null 일수 있음)

디버깅을 위하여 todosToday 변수도 함께 출력하도록 코드를 수정하였다. 

console.log("현재 선택날짜: ", date, todosToday)
console.log("날짜비교: ", date.getTime(), today.getTime() < getToday(new Date()).getTime())

할일을 추가할때 dateOfTodo 필드에 현재 할일목록이 디스플레이되고 있는 날짜를 설정하였다. 이렇게 하면 생성시각이 아니라 현재 할일목록이 디스플레이되는 날짜 기준으로 필터링하면 된다.  

if(todos.filter(todo => todo.title === trimedText).length > 0){
    setTodoText('중복된 할일입니다.')
    setWarning(true)
}else{
    const newTodo = {
      title: trimedText,
      category: category.current || '자기계발', // 선택한 카테고리 설정 
      isDone: false,
      createdAt: getCurrentTime(), // 클라이언트 기준이 아니라 서버기준 저장시각,
      dateOfTodo: date // createdAt 기준으로 todo 를 필터링하는게 아니라 해당 필드 기준으로 걸러냄 (추가)
    }
    await addData('todos', newTodo)
    Keyboard.dismiss() // 추가버튼 클릭시 키보드 감추기 
    setTodoText('') // 입력창 초기화
    category.current = '' // 카테고리 초기화 
}

 

* 로직오류 수정후 테스트하기

11월 9일과 11월 15일 각각에 할일을 추가하였다. 모두 오늘(11월 6일)을 기준으로 미래에 해당하지만, 각 날짜에 제대로 할일이 추가되는 것을 확인할 수 있다. 투두 생성시각은 11월 6일(오늘날짜)로 나타나고 있다. 

테스트 1
테스트 2

 

728x90