diff --git a/app.config.js b/app.config.js
index d9eca1a..0596a1f 100644
--- a/app.config.js
+++ b/app.config.js
@@ -76,7 +76,7 @@ const expoConfig = {
backgroundColor: '#ffffff',
},
package: 'com.safezone.onestep',
- versionCode: 7,
+ versionCode: 5,
softwareKeyboardLayoutMode: 'pan',
},
web: {
@@ -125,7 +125,7 @@ const expoConfig = {
],
'./plugins/withStaticFrameworks',
],
- runtimeVersion: '1.0.1',
+ runtimeVersion: '1.0.0',
updates: {
url: 'https://u.expo.dev/63f6bbd9-1594-44b3-b161-0e0051413ef0',
},
diff --git a/app/(tabs)/_layout.jsx b/app/(tabs)/_layout.jsx
index bee5029..cd7d929 100644
--- a/app/(tabs)/_layout.jsx
+++ b/app/(tabs)/_layout.jsx
@@ -1,4 +1,3 @@
-import TextInputProvider from '@/contexts/textInputContext';
import '@/locales/index';
import { Tabs } from 'expo-router';
import React from 'react';
@@ -19,38 +18,35 @@ const inboxIcon = ({ color, size }) => (
const TabLayout = () => {
const { t } = useTranslation();
return (
-
-
+
-
-
-
-
+ />
+
+
);
};
diff --git a/app/(tabs)/index.jsx b/app/(tabs)/index.jsx
index 5fd1f90..7facb97 100644
--- a/app/(tabs)/index.jsx
+++ b/app/(tabs)/index.jsx
@@ -28,6 +28,8 @@ import { scale, verticalScale } from 'react-native-size-matters';
const TodayView = () => {
const { i18n } = useTranslation();
const { userId } = useContext(LoginContext);
+ const [isDragging, setIsDragging] = React.useState(false);
+
const getLoadingText = () => {
if (i18n.language === 'ko') {
return `투두`;
@@ -40,11 +42,18 @@ const TodayView = () => {
userId: userId,
});
- const renderCategoriesTodo = ({ item }) => {
+ const renderCategoriesTodo = ({ item, index }) => {
+ const isLastItem = index === categoriesData.length - 1;
+
return (
-
-
-
+
+
+ setIsDragging(true)}
+ onDragEnd={() => setIsDragging(false)}
+ />
);
};
@@ -56,14 +65,14 @@ const TodayView = () => {
-
-
+
+
{
renderItem={renderCategoriesTodo}
keyExtractor={item => item.id}
contentContainerStyle={styles.flatList}
+ scrollEnabled={!isDragging}
/>
-
-
-
-
+
+
+
+
@@ -104,6 +114,9 @@ const styles = StyleSheet.create({
paddingHorizontal: scale(20),
paddingTop: verticalScale(20),
},
+ lastCategoryContainer: {
+ paddingBottom: verticalScale(90),
+ },
});
export default TodayView;
diff --git a/app/categoryView/categoryListView.jsx b/app/categoryView/categoryListView.jsx
index 9890adb..d610f79 100644
--- a/app/categoryView/categoryListView.jsx
+++ b/app/categoryView/categoryListView.jsx
@@ -44,11 +44,12 @@ const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
- paddingHorizontal: scale(20),
backgroundColor: 'white',
},
list: {
backgroundColor: 'white',
+ paddingHorizontal: scale(20),
+
flex: 1,
},
});
diff --git a/app/settingsView/settingsAccountView.tsx b/app/settingsView/settingsAccountView.tsx
index 18dc24d..a77cd8f 100644
--- a/app/settingsView/settingsAccountView.tsx
+++ b/app/settingsView/settingsAccountView.tsx
@@ -1,5 +1,6 @@
import '@/locales/index';
import { IndexPath, Layout, Menu, MenuItem } from '@ui-kitten/components';
+import React from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { SafeAreaView, StyleSheet } from 'react-native';
diff --git a/app/settingsView/settingsView.jsx b/app/settingsView/settingsView.jsx
index 89c2c59..54354fd 100644
--- a/app/settingsView/settingsView.jsx
+++ b/app/settingsView/settingsView.jsx
@@ -8,7 +8,7 @@ import {
useTheme,
} from '@ui-kitten/components';
import { useRouter } from 'expo-router';
-import React, { useState } from 'react';
+import React, { useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import ConfirmModal from '@/components/common/molecules/ConfirmModal';
import { api as Api } from '@/utils/api';
@@ -25,6 +25,7 @@ import {
} from 'react-native';
import { heightPercentage, widthPercentage } from '@/utils/responsiveSize';
import fontStyles from '@/theme/fontStyles';
+import { LoginContext } from '@/contexts/LoginContext';
const privacyPolicyUrl =
'https://swm-onestep.github.io/posts/%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8-copy/';
const termsOfServiceUrl =
@@ -45,6 +46,7 @@ const renderCurrentStatus = currentStatus => () => (
);
const SettingsView = () => {
+ const { userName } = useContext(LoginContext);
const { t } = useTranslation();
const theme = useTheme();
const { clear: clearStorage } = useStorage();
@@ -175,7 +177,7 @@ const SettingsView = () => {
{t('views.settingsView.greeting')}
- user
+ {userName}
diff --git a/components/InboxSubTodo.jsx b/components/InboxSubTodo.jsx
deleted file mode 100644
index 2a1033d..0000000
--- a/components/InboxSubTodo.jsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { TextInputContext } from '@/contexts/textInputContext';
-import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations';
-import { Icon, Input, ListItem } from '@ui-kitten/components';
-import React, { useContext, useState } from 'react';
-import { Text, TouchableOpacity } from 'react-native';
-import { useTheme } from 'react-native-elements';
-import TodoModal from './TodoModal';
-
-const InboxSubTodo = ({ item }) => {
- const [isEditing, setIsEditing] = useState(false);
- const [content, setContent] = useState(item.content);
- const theme = useTheme();
- const [modalVisible, setModalVisible] = useState(false);
- const { mutate: updateInboxSubTodo } = useSubTodoUpdateMutation();
- const { setInboxTextInputOpen } = useContext(TextInputContext);
-
- const handleEdit = () => {
- setIsEditing(true);
- setInboxTextInputOpen(false);
- setModalVisible(false);
- };
-
- const handleInboxSubTodoUpdate = () => {
- const updatedData = {
- todoId: item.id,
- content: content,
- };
- updateInboxSubTodo(updatedData);
- };
-
- const outlineIcon = props => {
- return (
- setModalVisible(true)}>
-
-
- );
- };
-
- const settingIcon = props => {
- return (
- setModalVisible(true)}>
-
-
- );
- };
-
- return (
- <>
- setContent(value)}
- onSubmitEditing={() => {
- handleInboxSubTodoUpdate();
- setIsEditing(false);
- setInboxTextInputOpen(true);
- }}
- autoFocus={true}
- />
- ) : (
- {item.content}
- )
- }
- key={item.id}
- accessoryLeft={props => outlineIcon(props)}
- accessoryRight={props => settingIcon(props)}
- onPress={() => setModalVisible(true)}
- style={{ paddingLeft: 40 }}
- />
-
- >
- );
-};
-
-export default InboxSubTodo;
diff --git a/components/WeeklyCalendar.jsx b/components/WeeklyCalendar.jsx
index da98a56..28671ea 100644
--- a/components/WeeklyCalendar.jsx
+++ b/components/WeeklyCalendar.jsx
@@ -13,79 +13,203 @@ import { Icon, Layout, Text, useTheme } from '@ui-kitten/components';
import { View } from 'react-native';
import moment from 'moment';
import 'moment/locale/ko';
-import React, { useContext, useEffect, useState } from 'react';
+import React, {
+ useContext,
+ useEffect,
+ useState,
+ useMemo,
+ useCallback,
+ memo,
+} from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity } from 'react-native';
import fontStyles from '../theme/fontStyles';
import { moderateScale, scale, verticalScale } from 'react-native-size-matters';
import useTodosQuery from '@/hooks/api/useTodoQuery';
+import useCategoriesQuery from '@/hooks/api/useCategoriesQuery';
-const WeeklyCalendar = () => {
+// 날짜 변환 딕셔너리를 컴포넌트 외부로 이동
+const convertDictionary = {
+ 월: 'Mon',
+ 화: 'Tue',
+ 수: 'Wed',
+ 목: 'Thu',
+ 금: 'Fri',
+ 토: 'Sat',
+ 일: 'Sun',
+};
+
+// 날짜 아이템 컴포넌트 분리 및 메모이제이션
+const DayItem = memo(
+ ({ date, selectedDate, theme, todoCount, onDateSelect, userId, i18n }) => {
+ const convertWeekDates = useCallback(
+ convertedDate => {
+ if (i18n.language === 'ko') {
+ return convertedDate.format('ddd');
+ }
+ return convertDictionary[convertedDate.format('ddd')];
+ },
+ [i18n.language],
+ );
+
+ const handlePress = useCallback(() => {
+ handleLogEvent(WEEKLYCALENDAR_DAYITEM_CLICK_EVENT, {
+ time: new Date().toISOString(),
+ userId,
+ day: date.format('YYYY-MM-DD'),
+ });
+ onDateSelect(date);
+ }, [date, userId, onDateSelect]);
+
+ return (
+
+
+ {convertWeekDates(date)}
+
+
+
+
+ {todoCount}
+
+
+
+ {date.format('D')}
+
+
+
+ );
+ },
+);
+
+const WeeklyCalendar = memo(() => {
const { selectedDate, setSelectedDate } = useContext(DateContext);
- const [currentDate, setcurrentDate] = useState(moment());
+ const [currentDate, setCurrentDate] = useState(moment());
const theme = useTheme();
const { userId } = useContext(LoginContext);
- const todos = useTodosQuery().data;
- // const [todos, setTodos] = useState(fedchedTodos.data);
- const getWeekDates = date => {
+ const { data: todos = [] } = useTodosQuery();
+ const { data: categories = [] } = useCategoriesQuery();
+ const { i18n } = useTranslation();
+
+ // 주간 날짜 계산 메모이제이션
+ const getWeekDates = useCallback(date => {
const start = date.clone().startOf('ISOWeek');
- const r = Array.from({ length: 7 }, (_, i) =>
+ return Array.from({ length: 7 }, (_, i) =>
moment(convertGmtToKst(new Date(start.clone().add(i, 'days')))),
);
- return r;
- };
- const [weekDates, setwWeekDates] = useState(getWeekDates(currentDate));
- const { i18n } = useTranslation();
+ }, []);
- const navigateWeek = direction => {
- setcurrentDate(prevDate =>
+ const weekDates = useMemo(
+ () => getWeekDates(currentDate),
+ [currentDate, getWeekDates],
+ );
+
+ const navigateWeek = useCallback(direction => {
+ setCurrentDate(prevDate =>
direction === 'next'
? prevDate.clone().add(7, 'd')
: prevDate.clone().subtract(7, 'd'),
);
- };
- useEffect(() => {
- setwWeekDates(getWeekDates(currentDate));
- }, [currentDate]);
- useEffect(() => {
- moment().isBetween(weekDates[0], weekDates[6])
- ? setSelectedDate(currentDate)
- : setSelectedDate(weekDates[0]);
- }, [weekDates, setSelectedDate, currentDate]);
+ }, []);
- const handleDateSelect = date => {
- setSelectedDate(date);
- };
+ const handleDateSelect = useCallback(
+ date => {
+ setSelectedDate(date);
+ },
+ [setSelectedDate],
+ );
- const convertMonthAndYear = date => {
+ const convertMonthAndYear = useCallback(date => {
return date.format('yyyy.MM');
- };
+ }, []);
- const convertDictionary = {
- 월: 'Mon',
- 화: 'Tue',
- 수: 'Wed',
- 목: 'Thu',
- 금: 'Fri',
- 토: 'Sat',
- 일: 'Sun',
- };
+ const convertCalendarType = useCallback(() => {
+ return i18n.language === 'ko' ? '주' : 'W';
+ }, [i18n.language]);
- const convertWeekDates = date => {
- if (i18n.language === 'ko') {
- return date.format('ddd');
- } else if (i18n.language === 'en') {
- return convertDictionary[date.format('ddd')];
- }
- };
+ // 주간 이동 버튼 핸들러
+ const handleNavigateWeek = useCallback(
+ direction => {
+ handleLogEvent(WEEKLYCALENDAR_NAVIGATEWEEK_CLICK_EVENT, {
+ time: new Date().toISOString(),
+ userId,
+ week: selectedDate.format('YYYY-MM-DD'),
+ direction,
+ });
+ navigateWeek(direction);
+ },
+ [navigateWeek, userId, selectedDate],
+ );
- const convertCalendarType = () => {
- if (i18n.language === 'ko') {
- return '주';
- } else if (i18n.language === 'en') {
- return 'W';
+ useEffect(() => {
+ if (moment().isBetween(weekDates[0], weekDates[6])) {
+ setSelectedDate(currentDate);
+ } else {
+ setSelectedDate(weekDates[0]);
}
- };
+ }, [weekDates, setSelectedDate, currentDate]);
+
+ // 일주일치 할일 필터링을 위한 메모이제이션 추가
+ const filteredWeekTodos = useMemo(() => {
+ const startDate = weekDates[0].format('YYYY-MM-DD');
+ const endDate = weekDates[6].format('YYYY-MM-DD');
+
+ return todos.filter(todo => {
+ // 카테고리 체크
+ const isCategoryValid = categories.some(
+ category => category.id === todo.categoryId,
+ );
+ // 날짜가 해당 주에 포함되는지 체크
+ const isDateInRange = moment(todo.date).isBetween(
+ startDate,
+ endDate,
+ 'day',
+ '[]',
+ );
+ // 완료되지 않은 할일만 필터링
+ return !todo.isCompleted && isCategoryValid && isDateInRange;
+ });
+ }, [todos, categories, weekDates]);
+
+ // DayItem 컴포넌트에 전달할 할일 개수 계산 함수
+ const getTodoCountForDate = useCallback(
+ date => {
+ return filteredWeekTodos.filter(todo =>
+ isTodoIncludedInTodayView(todo.date, date.format('YYYY-MM-DD')),
+ ).length;
+ },
+ [filteredWeekTodos],
+ );
return (
@@ -98,35 +222,14 @@ const WeeklyCalendar = () => {
- {
- handleLogEvent(WEEKLYCALENDAR_NAVIGATEWEEK_CLICK_EVENT, {
- time: new Date().toISOString(),
- userId: userId,
- week: selectedDate.format('YYYY-MM-DD'),
- direction: 'prev',
- });
- navigateWeek('prev');
- }}
- >
+ handleNavigateWeek('prev')}>
- {
- navigateWeek('next');
-
- handleLogEvent(WEEKLYCALENDAR_NAVIGATEWEEK_CLICK_EVENT, {
- time: new Date().toISOString(),
- userId: userId,
- week: selectedDate.format('YYYY-MM-DD'),
- direction: 'next',
- });
- }}
- >
+ handleNavigateWeek('next')}>
{
{weekDates.map((date, index) => (
-
-
- {convertWeekDates(date)}
-
- {
- handleLogEvent(WEEKLYCALENDAR_DAYITEM_CLICK_EVENT, {
- time: new Date().toISOString(),
- userId: userId,
- day: date.format('YYYY-MM-DD'),
- });
- handleDateSelect(date);
- }}
- style={{
- ...styles.touchBox,
- backgroundColor: date.isSame(selectedDate, 'day')
- ? theme.Blue01
- : 'transparent',
- }}
- >
-
-
- {
- todos.filter(
- todo =>
- isTodoIncludedInTodayView(
- todo.date,
- date.format('YYYY-MM-DD'),
- ) && !todo.isCompleted,
- ).length
- }
-
-
-
- {date.format('D')}
-
-
-
+
))}
);
-};
+});
+// 스타일은 동일하게 유지
const styles = StyleSheet.create({
background: {
display: 'flex',
diff --git a/components/categoryView/CategoryMainItem.jsx b/components/categoryView/CategoryMainItem.jsx
index e26fbba..30fab8a 100644
--- a/components/categoryView/CategoryMainItem.jsx
+++ b/components/categoryView/CategoryMainItem.jsx
@@ -1,7 +1,6 @@
-import { TextInputContext } from '@/contexts/textInputContext';
+import useTextInputStore from '@/contexts/textInputStore';
import colors from '@/theme/theme.json';
import { Icon } from '@ui-kitten/components';
-import { useContext } from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
import { scale, verticalScale } from 'react-native-size-matters';
import CategoryButton from './CategoryButton';
@@ -12,7 +11,13 @@ const CategoryMainItem = ({ item, isToday = true }) => {
setTodayActivatedCategoryId,
setInboxTextInputOpen,
setInboxActivatedCategoryId,
- } = useContext(TextInputContext);
+ } = useTextInputStore(state => ({
+ setTodayTextInputOpen: state.setTodayTextInputOpen,
+ setTodayActivatedCategoryId: state.setTodayActivatedCategoryId,
+ setInboxTextInputOpen: state.setInboxTextInputOpen,
+ setInboxActivatedCategoryId: state.setInboxActivatedCategoryId,
+ }));
+
const handlePress = () => {
if (isToday) {
setTodayTextInputOpen(true);
diff --git a/components/common/molecules/EditableTextField.tsx b/components/common/molecules/EditableTextField.tsx
index 3d6aa46..982b20f 100644
--- a/components/common/molecules/EditableTextField.tsx
+++ b/components/common/molecules/EditableTextField.tsx
@@ -1,38 +1,104 @@
import { Text } from '@ui-kitten/components';
-import React, { useState } from 'react';
+import React, {
+ useState,
+ useCallback,
+ useContext,
+ useEffect,
+ useRef,
+} from 'react';
import { StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native';
import { Todo } from '../../../types/todo';
import { moderateScale, scale } from 'react-native-size-matters';
+import { memo } from 'react';
+import { useTodoUpdateMutation } from '@/hooks/api/useTodoMutations';
+import {
+ NativeSyntheticEvent,
+ TextInputSubmitEditingEventData,
+} from 'react-native';
+import { AIBottomSheetContext } from '@/contexts/AIBottomSheetProvider';
+import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations';
interface EditableListItemTitleProps {
isEditing: boolean;
- handleSubmitEditing: (content) => void;
+ setIsEditing: (isEditing: boolean) => void;
item: Todo;
inputStyles?: StyleProp | null;
textStyles?: StyleProp | null;
+ isTodo?: boolean;
+ handleSubmitEditing?: (
+ e: NativeSyntheticEvent,
+ ) => void;
}
-const EditableTextField = ({
- isEditing,
- handleSubmitEditing,
- item,
- inputStyles = styles.input,
- textStyles = styles.text,
-}: EditableListItemTitleProps) => {
- const [content, setContent] = useState(item.content);
- return isEditing ? (
- handleSubmitEditing(content)}
- autoFocus={true}
- style={inputStyles}
- />
- ) : (
- {item.content}
- );
+const handleTodoContentUpdate = (content, item, updateTodo) => {
+ const updatedData = {
+ todoId: item.id,
+ content: content,
+ };
+ updateTodo(updatedData);
};
+const EditableTextField = memo(
+ ({
+ isEditing,
+ setIsEditing,
+ item,
+ inputStyles = styles.input,
+ textStyles = styles.text,
+ isTodo = true,
+ }: EditableListItemTitleProps) => {
+ const [content, setContent] = useState(item.content);
+ const { mutate: updateTodo } = useTodoUpdateMutation();
+ const { mutate: updateSubTodo } = useSubTodoUpdateMutation();
+ const { bottomSheetRef } = useContext(AIBottomSheetContext);
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (isEditing) {
+ bottomSheetRef.current?.close();
+ const timer = setTimeout(() => {
+ inputRef.current?.focus();
+ }, 100);
+ return () => clearTimeout(timer);
+ }
+ }, [isEditing, bottomSheetRef]);
+
+ const handleTodoListItemSubmitEditing = useCallback(
+ async (e: NativeSyntheticEvent) => {
+ if (isTodo) {
+ handleTodoContentUpdate(e.nativeEvent.text, item, updateTodo);
+ } else {
+ const updatedData = {
+ subtodoId: item.id,
+ content: e.nativeEvent.text,
+ };
+ updateSubTodo(updatedData);
+ }
+ await new Promise(resolve => setTimeout(resolve, 100)); // 0.1초 대기
+ setIsEditing(false);
+ },
+ [item, setIsEditing, updateTodo, updateSubTodo, isTodo],
+ );
+
+ const onChangeText = useCallback((text: string) => {
+ setContent(text);
+ }, []);
+ return isEditing ? (
+ await handleTodoListItemSubmitEditing(e)}
+ style={inputStyles}
+ autoFocus={true}
+ onFocus={() => bottomSheetRef.current?.close()}
+ />
+ ) : (
+ {content}
+ );
+ },
+);
+
export default EditableTextField;
const styles = StyleSheet.create({
diff --git a/components/common/molecules/InboxEditableTextField.tsx b/components/common/molecules/InboxEditableTextField.tsx
new file mode 100644
index 0000000..3d6aa46
--- /dev/null
+++ b/components/common/molecules/InboxEditableTextField.tsx
@@ -0,0 +1,49 @@
+import { Text } from '@ui-kitten/components';
+import React, { useState } from 'react';
+import { StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native';
+import { Todo } from '../../../types/todo';
+import { moderateScale, scale } from 'react-native-size-matters';
+
+interface EditableListItemTitleProps {
+ isEditing: boolean;
+ handleSubmitEditing: (content) => void;
+ item: Todo;
+ inputStyles?: StyleProp | null;
+ textStyles?: StyleProp | null;
+}
+
+const EditableTextField = ({
+ isEditing,
+ handleSubmitEditing,
+ item,
+ inputStyles = styles.input,
+ textStyles = styles.text,
+}: EditableListItemTitleProps) => {
+ const [content, setContent] = useState(item.content);
+ return isEditing ? (
+ handleSubmitEditing(content)}
+ autoFocus={true}
+ style={inputStyles}
+ />
+ ) : (
+ {item.content}
+ );
+};
+
+export default EditableTextField;
+
+const styles = StyleSheet.create({
+ text: {
+ paddingLeft: scale(8),
+ fontSize: moderateScale(14),
+ },
+ input: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingLeft: scale(8),
+ fontSize: moderateScale(14),
+ },
+});
diff --git a/components/inboxView/inboxTodos/InboxTodos.tsx b/components/inboxView/inboxTodos/InboxTodos.tsx
index 088076e..8899780 100644
--- a/components/inboxView/inboxTodos/InboxTodos.tsx
+++ b/components/inboxView/inboxTodos/InboxTodos.tsx
@@ -1,6 +1,5 @@
import { LoginContext } from '@/contexts/LoginContext';
-import { TextInputContext } from '@/contexts/textInputContext';
-import { useTodoUpdateMutation } from '@/hooks/api/useTodoMutations';
+import useTextInputStore from '@/contexts/textInputStore';
import useCreateInboxTodo from '@/hooks/todo/useCreateInboxTodo';
import useFilteredInboxTodos from '@/hooks/todo/useFilteredInboxTodo';
import '@/locales/index';
@@ -14,58 +13,37 @@ import {
INBOXVIEW_SCROLL_EVENT,
TODAYVIEW_TEXTINPUT_SUBMIT_EVENT,
} from '@/utils/logEvent';
-import { Icon } from '@ui-kitten/components';
-import { LexoRank } from 'lexorank';
+
import React, { Fragment, useContext } from 'react';
-import { useTranslation } from 'react-i18next';
-import {
- KeyboardAvoidingView,
- StyleSheet,
- TextInput,
- View,
-} from 'react-native';
-import DraggableFlatList, {
- ScaleDecorator,
-} from 'react-native-draggable-flatlist';
-import { GestureHandlerRootView } from 'react-native-gesture-handler';
-import { KeyboardAccessoryView } from 'react-native-keyboard-accessory';
+import { KeyboardAvoidingView, StyleSheet } from 'react-native';
+
+import { FlatList, GestureHandlerRootView } from 'react-native-gesture-handler';
import { moderateScale, scale, verticalScale } from 'react-native-size-matters';
import InboxTodo from './inboxTodo/InboxTodo';
+import TodoInput from '@/components/todayView/TextInput';
const InboxTodos = ({ todosData, categoryId }) => {
- const { t } = useTranslation();
const { userId } = useContext(LoginContext);
const inboxCurrentTodos = useFilteredInboxTodos(todosData, categoryId);
- const { mutate: updateInboxTodo } = useTodoUpdateMutation();
const {
isInboxTextInputOpen,
inboxActivatedCategoryId,
setInboxTextInputOpen,
- } = useContext(TextInputContext);
+ } = useTextInputStore(state => ({
+ isInboxTextInputOpen: state.isInboxTextInputOpen,
+ inboxActivatedCategoryId: state.inboxActivatedCategoryId,
+ setInboxTextInputOpen: state.setInboxTextInputOpen,
+ }));
const { input, setInput, handleSubmit } = useCreateInboxTodo(
userId,
categoryId,
);
- const renderTodo = ({ item, drag, isActive }) => {
- return (
-
-
-
- );
+ const renderTodo = ({ item }) => {
+ return ;
};
- const handleDragEnd = async ({ data }) => {
- const updatedData = data.map(item => {
- return {
- todoId: item.id,
- order: LexoRank.parse(item.order).toString(),
- };
- });
- updateInboxTodo(updatedData);
- };
- //TODO: order 순서 생각해보니까 이제 서버에서 하잖아? 일단 전체 투두에서 계속 마지막으로 붙이는 식으로 구현
const handleInputSubmit = async () => {
handleLogEvent(TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, {
time: new Date().toISOString(),
@@ -77,42 +55,27 @@ const InboxTodos = ({ todosData, categoryId }) => {
};
return (
-
+
- renderTodo({ item })}
keyExtractor={item => item.id.toString()}
onScroll={() => handleScroll(INBOXVIEW_SCROLL_EVENT, userId)}
scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE}
/>
{isInboxTextInputOpen && categoryId === inboxActivatedCategoryId ? (
-
-
-
-
-
-
+
) : null}
@@ -123,21 +86,19 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
- keyboardInputContainer: {
- backgroundColor: 'white',
- borderTopWidth: 0,
- },
- mainContainer: {
+ keyboardAvoidingView: {
flex: 1,
backgroundColor: 'white',
},
flatListContainer: {
flex: 1,
},
- keyboardAvoidingView: {
- width: '100%',
- position: 'absolute',
- bottom: 0,
+ keyboardInputContainer: {
+ backgroundColor: 'white',
+ borderTopWidth: 0,
+ },
+ mainContainer: {
+ flex: 1,
backgroundColor: 'white',
},
inputWrapper: {
diff --git a/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx b/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx
index 4082d04..031fa4d 100644
--- a/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx
+++ b/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx
@@ -1,16 +1,14 @@
-import SubTodoGenerateModal from '@/components/SubTodoGenerateModal';
-import GeneratedSubTodoList from '@/components/todayView/dailyTodos/dailyTodo/generatedSubTodoList/GeneratedSubTodoList';
-import { TextInputContext } from '@/contexts/textInputContext';
import '@/locales/index';
-import React, { useContext } from 'react';
+import React from 'react';
import { View } from 'react-native';
import useModal from '../../../../hooks/common/useModal';
import SubTodoInfo from './subTodoInfo/SubTodoInfo';
import SubTodoList from './subTodoList/SubTodoList';
import TodoListItem from './todoListItem/TodoListItem';
import useInboxTodo from './useInboxTodo';
+import useTextInputStore from '@/contexts/textInputStore';
-const InboxTodo = ({ item, drag, isActive }) => {
+const InboxTodo = ({ item }) => {
const {
isEditing,
setIsEditing,
@@ -18,16 +16,11 @@ const InboxTodo = ({ item, drag, isActive }) => {
setIsSubTodoToggleActivated,
subTodoInputActivated,
setSubTodoInputActivated,
- generatedSubTodos,
- setGeneratedSubTodos,
} = useInboxTodo();
- const { setInboxTextInputOpen } = useContext(TextInputContext);
-
- const {
- isVisible: isSubTodoGenerateModalVisible,
- setIsVisible: setIsSubTodoGenerateModalVisible,
- } = useModal();
+ const setInboxTextInputOpen = useTextInputStore(
+ state => state.setInboxTextInputOpen,
+ );
const { setIsVisible: setIsTodoModalVisible } = useModal();
@@ -41,11 +34,8 @@ const InboxTodo = ({ item, drag, isActive }) => {
0}
onEdit={handleEdit}
@@ -63,16 +53,6 @@ const InboxTodo = ({ item, drag, isActive }) => {
setSubTodoInputActivated={setSubTodoInputActivated}
/>
) : null}
-
-
);
};
diff --git a/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx b/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx
index 226b07c..236cfa3 100644
--- a/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx
+++ b/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx
@@ -1,5 +1,5 @@
import { LoginContext } from '@/contexts/LoginContext';
-import { TextInputContext } from '@/contexts/textInputContext';
+import useTextInputStore from '@/contexts/textInputStore';
import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations';
import {
handleLogEvent,
@@ -17,7 +17,9 @@ const InboxSubTodo = ({ item }) => {
const theme = useTheme();
const { mutate: updateSubTodo } = useSubTodoUpdateMutation();
const { userId } = useContext(LoginContext);
- const { setInboxTextInputOpen } = useContext(TextInputContext);
+ const { setInboxTextInputOpen } = useTextInputStore(
+ state => state.setInboxTextInputOpen,
+ );
const handleEdit = () => {
setIsEditing(true);
diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx
index d325777..197439c 100644
--- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx
+++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx
@@ -1,5 +1,4 @@
-import EditableTextField from '@/components/common/molecules/EditableTextField';
-import { heightPercentage, widthPercentage } from '@/utils/responsiveSize';
+import EditableTextField from '@/components/common/molecules/InboxEditableTextField';
import { Icon, ListItem } from '@ui-kitten/components';
import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
@@ -7,14 +6,11 @@ import { RenderItemParams } from 'react-native-draggable-flatlist';
import { Todo } from '../../../../../types/todo';
import TodoMoreMenu from './todoMoreMenu/TodoMoreMenu';
import useTodoListItem from './useTodoListItem';
+import { scale, verticalScale } from 'react-native-size-matters';
interface TodoListItemProps extends RenderItemParams {
isEditing: boolean;
setIsEditing: React.Dispatch>;
- setIsSubTodoGenerateModalVisible: React.Dispatch<
- React.SetStateAction
- >;
- setIsTodoModalVisible: React.Dispatch>;
onEdit: () => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bottomSheetRef: React.MutableRefObject;
@@ -30,7 +26,6 @@ const TodoListItem: React.FC = ({
isActive,
isEditing,
setIsEditing,
- setIsSubTodoGenerateModalVisible,
onEdit,
setSubTodoInputActivated,
}) => {
@@ -44,7 +39,6 @@ const TodoListItem: React.FC = ({
item,
isEditing,
setIsEditing,
- setIsSubTodoGenerateModalVisible,
});
const accessoryLeft = (props?) => {
@@ -63,7 +57,6 @@ const TodoListItem: React.FC = ({
const accessoryRight = () => {
return (
= ({
onLongPress={drag}
disabled={isActive}
title={title}
+ style={{ paddingHorizontal: 0 }}
/>
>
);
@@ -107,8 +101,7 @@ const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
- paddingBottom: heightPercentage(8),
- paddingTop: heightPercentage(8),
+ paddingVertical: verticalScale(10),
marginRight: 0,
paddingRight: 0,
},
@@ -119,13 +112,13 @@ const styles = StyleSheet.create({
flex: 1,
},
checkIcon: {
- width: widthPercentage(20),
- height: heightPercentage(20),
+ width: scale(20),
+ height: verticalScale(20),
},
settingIcon: {
- width: widthPercentage(20),
- height: heightPercentage(20),
- marginRight: widthPercentage(4),
+ width: scale(20),
+ height: verticalScale(20),
+ marginRight: scale(4),
},
touchableCheck: {
paddingTop: 0,
@@ -133,7 +126,7 @@ const styles = StyleSheet.create({
},
text: {
paddingTop: 0,
- paddingLeft: widthPercentage(4),
+ paddingLeft: scale(4),
},
input: {
paddingTop: 0,
diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
index 4246f11..7321beb 100644
--- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
+++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
@@ -46,25 +46,19 @@ const MenuIconButton = onPress => (
);
-const TodoMoreMenu = ({
- setIsSubTodoGenerateModalVisible,
- onEdit,
- item,
- setSubTodoInputActivated,
-}) => {
+const TodoMoreMenu = ({ onEdit, item, setSubTodoInputActivated }) => {
const {
handleEditPress,
handleDeletePress,
- handleGenerateSubTodoPress,
handleCreateSubTodoPress: handleAddSubTodoPress,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
handlePutTodoToInboxPress,
} = useTodoMoreMenu({
- setIsSubTodoGenerateModalVisible,
onEdit,
item,
setSubTodoInputActivated,
});
+
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const { openBottomSheet } = useContext(CalendarBottomSheetContext);
@@ -119,12 +113,12 @@ const TodoMoreMenu = ({
disabled={item.children.length > 0}
accessoryLeft={GenerateSubtodoIcon}
title= 0}
+ disabled={true}
titleText={t('components.todoMoreMenu.createSubTodoWithAi')}
/>
onPress={() => {
setSelectedTodo(item);
- handleGenerateSubTodoPress();
+ setVisible(false);
}}
style={styles.middleMenuItem}
/>
@@ -136,6 +130,7 @@ const TodoMoreMenu = ({
/>
onPress={() => {
handleAddSubTodoPress();
+ setVisible(false);
}}
style={styles.middleMenuItem}
/>
diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx
index dea7a21..614952b 100644
--- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx
+++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx
@@ -18,7 +18,6 @@ import { useContext } from 'react';
const useTodoMoreMenu = ({
item,
onEdit = () => {},
- setIsSubTodoGenerateModalVisible,
setSubTodoInputActivated,
}) => {
const { selectedCategory } = useContext(CategoryContext);
@@ -38,8 +37,8 @@ const useTodoMoreMenu = ({
const updatedTodo = {
date: kstDate,
- todo_id: item.id,
- category_id: selectedCategory,
+ todoId: item.id,
+ categoryId: selectedCategory,
};
updateTodoDate(updatedTodo);
};
@@ -70,10 +69,6 @@ const useTodoMoreMenu = ({
});
};
- const handleGenerateSubTodoPress = () => {
- setIsSubTodoGenerateModalVisible(true);
- };
-
const handleCreateSubTodoPress = () => {
handleLogEvent(TODOMODAL_CREATESUBTODO_CLICK_EVENT, {
time: new Date().toISOString(),
@@ -96,7 +91,6 @@ const useTodoMoreMenu = ({
handleEditPress,
handleDeletePress,
handleChaneDatePress,
- handleGenerateSubTodoPress,
handleCreateSubTodoPress,
handlePutTodoToInboxPress,
};
diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts b/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts
index b454cc4..d831e8f 100644
--- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts
+++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts
@@ -1,4 +1,4 @@
-import { TextInputContext } from '@/contexts/textInputContext';
+import useTextInputStore from '@/contexts/textInputStore';
import { useTheme } from '@ui-kitten/components';
import { useContext } from 'react';
import { LoginContext } from '../../../../../contexts/LoginContext';
@@ -10,16 +10,13 @@ import {
handleLogEvent,
} from '../../../../../utils/logEvent';
-const useTodoListItem = ({
- item,
- isEditing,
- setIsEditing,
- setIsSubTodoGenerateModalVisible,
-}) => {
+const useTodoListItem = ({ item, isEditing, setIsEditing }) => {
const { mutate: updateTodo } = useTodoUpdateMutation();
const { userId } = useContext(LoginContext);
const theme = useTheme();
- const { setInboxTextInputOpen } = useContext(TextInputContext);
+ const setInboxTextInputOpen = useTextInputStore(
+ state => state.setInboxTextInputOpen,
+ );
const checkIconlogPressEvent = () => {
handleLogEvent(DAILYTODO_TODOCOMPLETE_CLICK_EVENT, {
@@ -62,10 +59,6 @@ const useTodoListItem = ({
setInboxTextInputOpen(false);
};
- const handleGenerateIconPress = () => {
- setIsSubTodoGenerateModalVisible(true);
- };
-
const handleSettingIconPress = () => {
handleLogEvent(DAILYTODO_MEATBALLMENU_CLICK_EVENT, {
time: new Date().toISOString(),
@@ -81,7 +74,6 @@ const useTodoListItem = ({
setIsEditing,
handleTodoListItemPress,
handleTodoListItemSubmitEditing,
- handleGenerateIconPress,
handleSettingIconPress,
};
};
diff --git a/components/todayView/TextInput.tsx b/components/todayView/TextInput.tsx
new file mode 100644
index 0000000..3473971
--- /dev/null
+++ b/components/todayView/TextInput.tsx
@@ -0,0 +1,64 @@
+import React, { memo } from 'react';
+import { TextInput as RNTextInput, View, StyleSheet } from 'react-native';
+import { Icon } from '@ui-kitten/components';
+import { scale, verticalScale, moderateScale } from 'react-native-size-matters';
+import colors from '@/theme/theme.json';
+import { useTranslation } from 'react-i18next';
+
+interface TodoInputProps {
+ input: string;
+ setInput: (text: string) => void;
+ onSubmit: () => void;
+}
+
+const TodoInput = memo(({ input, setInput, onSubmit }: TodoInputProps) => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+
+ );
+});
+
+export default TodoInput;
+
+const styles = StyleSheet.create({
+ inputWrapper: {
+ width: '100%',
+ borderTopWidth: StyleSheet.hairlineWidth,
+ borderTopColor: colors.Gray02,
+ backgroundColor: 'white',
+ },
+ inputContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: scale(7),
+ paddingVertical: verticalScale(8),
+ },
+ checkIcon: {
+ width: scale(24),
+ height: verticalScale(24),
+ },
+ textInput: {
+ flex: 1,
+ backgroundColor: colors.Gray02,
+ borderRadius: moderateScale(4),
+ paddingVertical: verticalScale(8),
+ paddingHorizontal: scale(10),
+ },
+});
diff --git a/components/todayView/TodoList.tsx b/components/todayView/TodoList.tsx
new file mode 100644
index 0000000..89895e6
--- /dev/null
+++ b/components/todayView/TodoList.tsx
@@ -0,0 +1,79 @@
+import {
+ DEFAULT_SCROLL_EVENT_THROTTLE,
+ handleScroll,
+} from '@/utils/handleScroll';
+import { TODAYVIEW_SCROLL_EVENT } from '@/utils/logEvent';
+import React, { memo, forwardRef } from 'react';
+import { FlatList } from 'react-native-gesture-handler';
+import DraggableFlatList from 'react-native-draggable-flatlist';
+import { Todo } from '@/types/todo';
+import { StyleSheet } from 'react-native';
+
+const TodoList = memo(
+ forwardRef<
+ FlatList,
+ {
+ todos: Todo[];
+ renderItem: (info: {
+ item: Todo;
+ drag: () => void;
+ isActive: boolean;
+ }) => React.ReactNode;
+ onDragEnd: (params: { from: number; to: number; data: Todo[] }) => void;
+ onDragStart: () => void;
+ userId: string;
+ setTodos: (todos: Todo[]) => void;
+ }
+ >(
+ (
+ {
+ todos,
+ renderItem,
+ onDragEnd,
+ onDragStart,
+ userId,
+ setTodos,
+ }: {
+ todos: Todo[];
+ renderItem: (info: {
+ item: Todo;
+ drag: () => void;
+ isActive: boolean;
+ }) => React.ReactNode;
+ onDragEnd: (params: { from: number; to: number; data: Todo[] }) => void;
+ onDragStart: () => void;
+ userId: string;
+ setTodos: (todos: Todo[]) => void;
+ },
+ ref,
+ ) => {
+ return (
+ {
+ onDragEnd({ from, to, data });
+ setTodos(data);
+ }}
+ onDragBegin={onDragStart}
+ keyExtractor={item => item.id.toString()}
+ onScroll={() => handleScroll(TODAYVIEW_SCROLL_EVENT, userId)}
+ scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE}
+ simultaneousHandlers={[]}
+ activationDistance={20}
+ containerStyle={styles.flatListContainer}
+ extraData={todos}
+ />
+ );
+ },
+ ),
+);
+
+export default TodoList;
+
+const styles = StyleSheet.create({
+ flatListContainer: {
+ flex: 1,
+ },
+});
diff --git a/components/todayView/dailyTodos/DailyTodos.tsx b/components/todayView/dailyTodos/DailyTodos.tsx
index 61a8e45..8ee1069 100644
--- a/components/todayView/dailyTodos/DailyTodos.tsx
+++ b/components/todayView/dailyTodos/DailyTodos.tsx
@@ -1,35 +1,28 @@
import { DateContext } from '@/contexts/DateContext';
import { LoginContext } from '@/contexts/LoginContext';
-import { TextInputContext } from '@/contexts/textInputContext';
+import useTextInputStore from '@/contexts/textInputStore';
import useCreateTodo from '@/hooks/todo/useCreateTodo';
import useFilteredTodos from '@/hooks/todo/useFilteredTodo';
import useHandleDrag from '@/hooks/todo/useHandleDrag';
import '@/locales/index';
-import colors from '@/theme/theme.json';
-import {
- DEFAULT_SCROLL_EVENT_THROTTLE,
- handleScroll,
-} from '@/utils/handleScroll';
import {
handleLogEvent,
- TODAYVIEW_SCROLL_EVENT,
TODAYVIEW_TEXTINPUT_SUBMIT_EVENT,
} from '@/utils/logEvent';
-import { Icon } from '@ui-kitten/components';
-import React, { useContext } from 'react';
-import { useTranslation } from 'react-i18next';
-import { StyleSheet, TextInput, View } from 'react-native';
-import DraggableFlatList from 'react-native-draggable-flatlist';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
+import { StyleSheet, View } from 'react-native';
+import { ScaleDecorator } from 'react-native-draggable-flatlist';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
-import { moderateScale, scale, verticalScale } from 'react-native-size-matters';
import DailyTodo from './dailyTodo/DailyTodo';
+import TodoList from '../TodoList';
+import TodoInput from '../TextInput';
+import useListRefStore from '@/contexts/listRefStore';
-const DailyTodos = ({ todosData, categoryId }) => {
+const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => {
const { userId } = useContext(LoginContext);
const { selectedDate } = useContext(DateContext);
- const { t } = useTranslation();
-
const currentTodos = useFilteredTodos(todosData, categoryId, selectedDate);
+ const [todos, setTodos] = useState(currentTodos);
const { input, setInput, handleSubmit } = useCreateTodo(
userId,
categoryId,
@@ -40,63 +33,72 @@ const DailyTodos = ({ todosData, categoryId }) => {
isTodayTextInputOpen,
todayActivatedCategoryId,
setTodayTextInputOpen,
- } = useContext(TextInputContext);
+ } = useTextInputStore();
const handleDragEnd = useHandleDrag();
- const handleInputSubmit = () => {
+ const { setTodoListRef } = useListRefStore();
+ const todoListRef = React.useRef(null);
+
+ useEffect(() => {
+ setTodoListRef(todoListRef);
+ }, [setTodoListRef]);
+
+ const handleInputSubmit = useCallback(() => {
handleLogEvent(TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, {
time: new Date().toISOString(),
- userId: userId,
+ userId,
});
handleSubmit();
setTodayTextInputOpen(false);
- };
+ todoListRef.current?.scrollToEnd();
+ }, [userId, handleSubmit, setTodayTextInputOpen]);
+
+ const handleDragEndWithCallback = useCallback(
+ ({ from, to, data }) => {
+ handleDragEnd({ from, to, data });
+ onDragEnd?.();
+ },
+ [handleDragEnd, onDragEnd],
+ );
- const renderTodo = ({ item, drag, isActive }) => {
- return (
-
- );
- };
+ const renderTodo = useCallback(
+ ({ item, drag, isActive }) => (
+
+
+
+ ),
+ [categoryId],
+ );
+
+ useEffect(() => {
+ setTodos(currentTodos);
+ }, [currentTodos]);
return (
- item.id.toString()}
- onScroll={() => handleScroll(TODAYVIEW_SCROLL_EVENT, userId)}
- scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE}
- simultaneousHandlers={[]}
- activationDistance={20}
- containerStyle={styles.flatListContainer}
+ onDragEnd={handleDragEndWithCallback}
+ onDragStart={onDragStart}
+ userId={userId}
/>
{isTodayTextInputOpen && categoryId === todayActivatedCategoryId && (
-
-
-
-
-
-
+
)}
@@ -111,40 +113,6 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: 'white',
},
- flatListContainer: {
- flex: 1,
- },
- keyboardAvoidingView: {
- width: '100%',
- position: 'absolute',
- bottom: 0,
- backgroundColor: 'white',
- },
- inputWrapper: {
- width: '100%',
- borderTopWidth: StyleSheet.hairlineWidth,
- borderTopColor: colors.Gray02,
- backgroundColor: 'white',
- },
- inputContainer: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingHorizontal: scale(7),
- paddingVertical: verticalScale(8),
- },
- checkIcon: {
- width: scale(24),
- height: verticalScale(24),
- },
- textInput: {
- flex: 1,
- backgroundColor: colors.Gray02,
- borderRadius: moderateScale(4),
- marginLeft: scale(4),
- paddingHorizontal: scale(8),
- height: scale(24),
- fontSize: moderateScale(14),
- },
});
export default DailyTodos;
diff --git a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx
index 7c1638a..8f8facd 100644
--- a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx
+++ b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx
@@ -1,9 +1,6 @@
import { View, Text, Pressable, StyleSheet } from 'react-native';
-import React, { useCallback, useContext, useMemo } from 'react';
-import BottomSheet, {
- BottomSheetBackdrop,
- BottomSheetView,
-} from '@gorhom/bottom-sheet';
+import React, { useContext, useMemo } from 'react';
+import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
import { DateContext } from '@/contexts/DateContext';
import { CalendarBottomSheetContext } from '@/contexts/CalendarBottomSheetProvider';
import {
@@ -29,7 +26,7 @@ const CalendarBottomSheet = ({ isTodo, item }) => {
);
const { mutate: updateTodoDate } = useTodoUpdateMutation();
const { mutate: updateSubTodoDate } = useSubTodoUpdateMutation();
- const snapPoints = useMemo(() => ['75%'], []);
+ const snapPoints = useMemo(() => ['90%'], []);
const { i18n } = useTranslation();
const handleDateUpdate = date => {
@@ -99,25 +96,12 @@ const CalendarBottomSheet = ({ isTodo, item }) => {
startDayOfWeek: 1,
});
- const renderBackdrop = useCallback(
- props => (
-
- ),
- [],
- );
-
return (
{
const {
- isEditing,
- setIsEditing,
isSubtodoToggleActivated,
setIsSubTodoToggleActivated,
subTodoInputActivated,
setSubTodoInputActivated,
- generatedSubTodos,
- setGeneratedSubTodos,
} = useDailyTodo();
- const { setTodayTextInputOpen } = useContext(TextInputContext);
- const {
- isVisible: isSubTodoGenerateModalVisible,
- setIsVisible: setIsSubTodoGenerateModalVisible,
- } = useModal();
-
- const { setIsVisible: setIsTodoModalVisible } = useModal();
-
- const handleEdit = () => {
- setIsEditing(true);
- setTodayTextInputOpen(false);
- setIsTodoModalVisible(false);
- };
-
return (
0}
- onEdit={handleEdit}
+ item={item}
setSubTodoInputActivated={setSubTodoInputActivated}
categoryId={categoryId}
/>
@@ -63,16 +34,6 @@ const DailyTodo = ({ item, drag, isActive, categoryId }) => {
setSubTodoInputActivated={setSubTodoInputActivated}
/>
) : null}
-
-
);
};
diff --git a/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx b/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx
index c3c3e62..d13629e 100644
--- a/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx
+++ b/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx
@@ -48,6 +48,7 @@ const SubTodoInfo: React.FC = ({
const styles = StyleSheet.create({
container: {
paddingTop: 0,
+ paddingRight: scale(2),
},
bottomContainer: {
flexDirection: 'row',
diff --git a/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx b/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx
index ddb437e..1c16016 100644
--- a/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx
+++ b/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx
@@ -1,6 +1,6 @@
import EditableTextField from '@/components/common/molecules/EditableTextField';
import { LoginContext } from '@/contexts/LoginContext';
-import { TextInputContext } from '@/contexts/textInputContext';
+import useTextInputStore from '@/contexts/textInputStore';
import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations';
import getIconFillColor from '@/utils/getIconFillColor';
import {
@@ -18,7 +18,9 @@ const DailySubTodo = ({ item }) => {
const [isEditing, setIsEditing] = useState(false);
const { mutate: updateSubTodo } = useSubTodoUpdateMutation();
const { userId } = useContext(LoginContext);
- const { setTodayTextInputOpen } = useContext(TextInputContext);
+ const setTodayTextInputOpen = useTextInputStore(
+ state => state.setTodayTextInputOpen,
+ );
const handleEdit = () => {
setIsEditing(true);
@@ -34,15 +36,6 @@ const DailySubTodo = ({ item }) => {
updateSubTodo(updatedData);
};
- const handleSubTodoUpdate = content => {
- const updatedData = {
- subtodoId: item.id,
- content: content,
- };
- updateSubTodo(updatedData);
- setIsEditing(false);
- };
-
const handleCheckIconPress = () => {
handleLogEvent(DAILYTODO_SUBTODOCOMPLETE_CLICK_EVENT, {
time: new Date().toISOString(),
@@ -75,10 +68,11 @@ const DailySubTodo = ({ item }) => {
<>
>
);
diff --git a/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx b/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx
index 451ccae..2fe560f 100644
--- a/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx
+++ b/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx
@@ -1,6 +1,6 @@
import colors from '@/theme/theme.json';
import { Icon, List } from '@ui-kitten/components';
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, TextInput, View } from 'react-native';
import { Todo } from '../../../../../types/todo';
@@ -27,6 +27,13 @@ const SubTodoList: React.FC = ({
const { handleSubtodoSubmit, subTodoInput, setSubtodoInput } = useSubTodoList(
{ item, setSubTodoInputActivated },
);
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (subTodoInputActivated) {
+ inputRef.current?.focus();
+ }
+ }, [subTodoInputActivated]);
return (
= ({
fill={colors.Gray02}
/>
{
diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx
index a16e3ba..d4a9b03 100644
--- a/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx
+++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx
@@ -1,6 +1,6 @@
import getIconFillColor from '@/utils/getIconFillColor';
import { Icon, ListItem, Text } from '@ui-kitten/components';
-import React from 'react';
+import React, { useCallback } from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
import { RenderItemParams } from 'react-native-draggable-flatlist';
import { moderateScale, scale, verticalScale } from 'react-native-size-matters';
@@ -24,80 +24,72 @@ interface TodoListItemProps extends RenderItemParams {
categoryId: number;
}
-const TodoListItem: React.FC = ({
- item,
- drag,
+const TodoListItem: React.FC = React.memo(
+ ({ item, drag, isActive, setSubTodoInputActivated, categoryId }) => {
+ const {
+ isEditing,
+ setIsEditing,
+ theme,
+ handleCheckIconPress,
+ handleTodoListItemPress,
+ } = useTodoListItem({
+ item,
+ });
- isActive,
- isEditing,
- setIsEditing,
- setIsSubTodoGenerateModalVisible,
- onEdit,
- setSubTodoInputActivated,
- categoryId,
-}) => {
- const {
- theme,
- handleCheckIconPress,
- handleTodoListItemPress,
- handleTodoListItemSubmitEditing,
- } = useTodoListItem({
- item,
- isEditing,
- setIsEditing,
- setIsSubTodoGenerateModalVisible,
- });
+ const accessoryLeft = useCallback(
+ (props?) => {
+ return (
+
+
+
+ );
+ },
+ [handleCheckIconPress, item.isCompleted],
+ );
- const accessoryLeft = (props?) => {
- return (
- handleCheckIconPress()}
- style={styles.touchableCheck}
- >
- {
+ return (
+
-
- );
- };
+ );
+ }, [item, setSubTodoInputActivated, categoryId, setIsEditing]);
- const accessoryRight = () => {
- return (
-
+ const title = useCallback(
+ () => (
+
+
+ {item.dueTime && (
+
+ {item.dueTime.split(':').slice(0, 2).join(':')}
+
+ )}
+
+ ),
+ [isEditing, item, setIsEditing, theme],
);
- };
- const title = () => (
-
-
- {item.dueTime && (
-
- {item.dueTime.split(':').slice(0, 2).join(':')}
-
- )}
-
- );
-
- return (
- <>
+ return (
= ({
disabled={isActive}
title={title}
/>
- >
- );
-};
+ );
+ },
+);
const styles = StyleSheet.create({
checkIcon: {
@@ -132,4 +124,4 @@ const styles = StyleSheet.create({
},
});
-export default TodoListItem;
+export default React.memo(TodoListItem);
diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
index f5e308a..c6ff9bc 100644
--- a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
+++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
@@ -49,11 +49,10 @@ const MenuIconButton = onPress => (
);
const TodoMoreMenu = ({
- setIsSubTodoGenerateModalVisible,
- onEdit,
item,
setSubTodoInputActivated,
categoryId,
+ setIsEditing,
}) => {
const {
handleEditPress,
@@ -61,11 +60,10 @@ const TodoMoreMenu = ({
handleCreateSubTodoPress: handleAddSubTodoPress,
handlePutTodoToInboxPress,
} = useTodoMoreMenu({
- setIsSubTodoGenerateModalVisible,
- onEdit,
item,
setSubTodoInputActivated,
categoryId,
+ setIsEditing,
});
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
@@ -73,7 +71,6 @@ const TodoMoreMenu = ({
const setSelectedTodo = useTodoStore(state => state.setSelectedTodo);
const { openBottomSheet: openAIBottomSheet } =
useContext(AIBottomSheetContext);
- useContext(AIBottomSheetContext);
const toggleMenu = useCallback(() => {
setVisible(true);
diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx
index 7578ca8..b720696 100644
--- a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx
+++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx
@@ -16,10 +16,9 @@ import { useContext } from 'react';
const useTodoMoreMenu = ({
item,
- onEdit = () => {},
- setIsSubTodoGenerateModalVisible,
setSubTodoInputActivated,
categoryId,
+ setIsEditing,
}) => {
const { userId } = useContext(LoginContext);
@@ -37,7 +36,7 @@ const useTodoMoreMenu = ({
const updatedTodo = {
date: kstDate,
- todo_id: item.id,
+ todoId: item.id,
category_id: categoryId,
};
updateTodoDate(updatedTodo);
@@ -49,7 +48,7 @@ const useTodoMoreMenu = ({
userId: userId,
todoId: item.id,
});
- onEdit();
+ setIsEditing(true);
};
const handleDeletePress = () => {
@@ -69,10 +68,6 @@ const useTodoMoreMenu = ({
});
};
- const handleGenerateSubTodoPress = () => {
- setIsSubTodoGenerateModalVisible(true);
- };
-
const handleCreateSubTodoPress = () => {
handleLogEvent(TODOMODAL_CREATESUBTODO_CLICK_EVENT, {
time: new Date().toISOString(),
@@ -95,7 +90,6 @@ const useTodoMoreMenu = ({
handleEditPress,
handleDeletePress,
handleChaneDatePress,
- handleGenerateSubTodoPress,
handleCreateSubTodoPress,
handlePutTodoToInboxPress,
};
diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts
index cb309ca..fd794c0 100644
--- a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts
+++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts
@@ -1,6 +1,5 @@
-import { TextInputContext } from '@/contexts/textInputContext';
import { useTheme } from '@ui-kitten/components';
-import { useContext } from 'react';
+import { useContext, useState } from 'react';
import { LoginContext } from '../../../../../contexts/LoginContext';
import { useTodoUpdateMutation } from '../../../../../hooks/api/useTodoMutations';
import {
@@ -10,16 +9,11 @@ import {
handleLogEvent,
} from '../../../../../utils/logEvent';
-const useTodoListItem = ({
- item,
- isEditing,
- setIsEditing,
- setIsSubTodoGenerateModalVisible,
-}) => {
+const useTodoListItem = ({ item }) => {
const { mutate: updateTodo } = useTodoUpdateMutation();
const { userId } = useContext(LoginContext);
const theme = useTheme();
- const { setTodayTextInputOpen } = useContext(TextInputContext);
+ const [isEditing, setIsEditing] = useState(false);
const checkIconlogPressEvent = () => {
handleLogEvent(DAILYTODO_TODOCOMPLETE_CLICK_EVENT, {
@@ -48,24 +42,6 @@ const useTodoListItem = ({
});
};
- const handleTodoContentUpdate = content => {
- const updatedData = {
- todoId: item.id,
- content: content,
- };
- updateTodo(updatedData);
- };
-
- const handleTodoListItemSubmitEditing = content => {
- handleTodoContentUpdate(content);
- setIsEditing(false);
- setTodayTextInputOpen(false);
- };
-
- const handleGenerateIconPress = () => {
- setIsSubTodoGenerateModalVisible(true);
- };
-
const handleSettingIconPress = () => {
handleLogEvent(DAILYTODO_MEATBALLMENU_CLICK_EVENT, {
time: new Date().toISOString(),
@@ -80,8 +56,6 @@ const useTodoListItem = ({
isEditing,
setIsEditing,
handleTodoListItemPress,
- handleTodoListItemSubmitEditing,
- handleGenerateIconPress,
handleSettingIconPress,
};
};
diff --git a/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx b/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx
index 723cd94..44514e9 100644
--- a/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx
+++ b/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx
@@ -1,15 +1,6 @@
-import React, {
- useCallback,
- useContext,
- useMemo,
- useState,
- useEffect,
-} from 'react';
+import React, { useContext, useMemo, useState, useEffect } from 'react';
import { View, StyleSheet, Pressable } from 'react-native';
-import BottomSheet, {
- BottomSheetBackdrop,
- BottomSheetView,
-} from '@gorhom/bottom-sheet';
+import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
import { LoginContext } from '@/contexts/LoginContext';
import {
Button,
@@ -29,6 +20,7 @@ import axios from 'axios';
import { API_PATH } from '@/utils/config';
import * as Sentry from '@sentry/react-native';
import { AIBottomSheetContext } from '@/contexts/AIBottomSheetProvider';
+import useListRefStore from '@/contexts/listRefStore';
const SubTodoGenerateBottomSheet = ({ item }) => {
const { accessToken } = useContext(LoginContext);
@@ -38,23 +30,12 @@ const SubTodoGenerateBottomSheet = ({ item }) => {
const theme = useTheme();
const { t } = useTranslation();
const { mutate: addSubTodo } = useSubTodoAddMutation();
+ const { todoListRef } = useListRefStore();
const snapPoints = useMemo(() => ['75%'], []);
const { bottomSheetRef } = useContext(AIBottomSheetContext);
- const renderBackdrop = useCallback(
- props => (
-
- ),
- [],
- );
-
useEffect(() => {
if (generatedSubTodos.length > 0) {
setSelectedIndexes(generatedSubTodos.map((_, index) => index));
@@ -95,7 +76,21 @@ const SubTodoGenerateBottomSheet = ({ item }) => {
date: generatedSubTodos[index].date,
todoId: generatedSubTodos[index].todo,
}));
- addSubTodo({ todoData: newSubTodos });
+
+ addSubTodo(
+ { todoData: newSubTodos },
+ {
+ onSuccess: () => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ if (todoListRef?.current) {
+ todoListRef.current?.scrollToEnd({ animated: true });
+ }
+ });
+ });
+ },
+ },
+ );
handleClose();
};
@@ -220,7 +215,6 @@ const SubTodoGenerateBottomSheet = ({ item }) => {
snapPoints={snapPoints}
index={-1}
enablePanDownToClose={true}
- backdropComponent={renderBackdrop}
>
{renderContent()}
diff --git a/contexts/LoginContext.js b/contexts/LoginContext.js
index b5929bd..31d5d82 100755
--- a/contexts/LoginContext.js
+++ b/contexts/LoginContext.js
@@ -10,6 +10,7 @@ const LoginProvider = ({ children }) => {
const [accessToken, setAccessToken] = useState();
const [refreshToken, setRefreshToken] = useState();
const [loginType, setLoginType] = useState();
+ const [userName, setUserName] = useState();
return (
{
setRefreshToken,
loginType,
setLoginType,
+ userName,
+ setUserName,
}}
>
{children}
diff --git a/contexts/listRefStore.ts b/contexts/listRefStore.ts
new file mode 100644
index 0000000..66d0e34
--- /dev/null
+++ b/contexts/listRefStore.ts
@@ -0,0 +1,18 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { create } from 'zustand';
+
+type ListRefStore = {
+ categoryListRef: React.RefObject | null;
+ setCategoryListRef: (ref: React.RefObject) => void;
+ todoListRef: React.RefObject | null;
+ setTodoListRef: (ref: React.RefObject) => void;
+};
+
+const useListRefStore = create(set => ({
+ categoryListRef: null,
+ setCategoryListRef: ref => set({ categoryListRef: ref }),
+ todoListRef: null,
+ setTodoListRef: ref => set({ todoListRef: ref }),
+}));
+
+export default useListRefStore;
diff --git a/contexts/textInputContext.js b/contexts/textInputContext.js
deleted file mode 100644
index bfc925c..0000000
--- a/contexts/textInputContext.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { createContext, useState } from 'react';
-
-export const TextInputContext = createContext();
-
-const TextInputProvider = ({ children }) => {
- const [isTodayTextInputOpen, setTodayTextInputOpen] = useState(false);
- const [todayActivatedCategoryId, setTodayActivatedCategoryId] =
- useState(null);
- const [isInboxTextInputOpen, setInboxTextInputOpen] = useState(false);
- const [inboxActivatedCategoryId, setInboxActivatedCategoryId] =
- useState(null);
-
- return (
-
- {children}
-
- );
-};
-
-export default TextInputProvider;
diff --git a/contexts/textInputStore.js b/contexts/textInputStore.js
new file mode 100644
index 0000000..8f6d0be
--- /dev/null
+++ b/contexts/textInputStore.js
@@ -0,0 +1,17 @@
+import { create } from 'zustand';
+
+const useTextInputStore = create(set => ({
+ isTodayTextInputOpen: false,
+ setTodayTextInputOpen: isOpen => set({ isTodayTextInputOpen: isOpen }),
+
+ todayActivatedCategoryId: null,
+ setTodayActivatedCategoryId: id => set({ todayActivatedCategoryId: id }),
+
+ isInboxTextInputOpen: false,
+ setInboxTextInputOpen: isOpen => set({ isInboxTextInputOpen: isOpen }),
+
+ inboxActivatedCategoryId: null,
+ setInboxActivatedCategoryId: id => set({ inboxActivatedCategoryId: id }),
+}));
+
+export default useTextInputStore;
diff --git a/contexts/todoEditStore.ts b/contexts/todoEditStore.ts
new file mode 100644
index 0000000..d36b15e
--- /dev/null
+++ b/contexts/todoEditStore.ts
@@ -0,0 +1,18 @@
+import { create } from 'zustand';
+
+interface TodoEditStore {
+ isEditing: boolean;
+ editingTodoId: number | null;
+ isSubTodoModalVisible: boolean;
+ setIsEditing: (isEditing: boolean) => void;
+ setEditingTodoId: (todoId: number | null) => void;
+}
+
+export const useTodoEditStore = create(set => ({
+ isEditing: false,
+ editingTodoId: null,
+ isSubTodoModalVisible: false,
+ isTodayTextInputOpen: false,
+ setIsEditing: isEditing => set({ isEditing }),
+ setEditingTodoId: todoId => set({ editingTodoId: todoId }),
+}));
diff --git a/hooks/api/useCategoriesQuery.js b/hooks/api/useCategoriesQuery.js
index 5f00f42..ae285eb 100644
--- a/hooks/api/useCategoriesQuery.js
+++ b/hooks/api/useCategoriesQuery.js
@@ -15,6 +15,8 @@ const useCategoriesQuery = userId => {
suspense: true,
refetchInterval: 60000,
refetchIntervalInBackground: true,
+ cacheTime: 180000,
+ staleTime: 30000,
});
};
diff --git a/hooks/api/useInboxTodoQuery.js b/hooks/api/useInboxTodoQuery.js
index faa94a6..a29a4d2 100644
--- a/hooks/api/useInboxTodoQuery.js
+++ b/hooks/api/useInboxTodoQuery.js
@@ -14,6 +14,8 @@ const useInboxTodoQuery = userId => {
queryKey: [INBOX_QUERY_KEY],
queryFn: () => fetcher(userId),
suspense: true,
+ cacheTime: 180000,
+ staleTime: 30000,
});
};
diff --git a/hooks/api/useTodoMutations.js b/hooks/api/useTodoMutations.js
deleted file mode 100644
index 3ef1b5a..0000000
--- a/hooks/api/useTodoMutations.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import { api as Api } from '@/utils/api';
-import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { INBOX_QUERY_KEY } from './useInboxTodoQuery';
-import { TODO_QUERY_KEY } from './useTodoQuery';
-
-// 생성 (Add Todo)
-const addTodoFetcher = async todoData => {
- const data = await Api.addTodo(todoData);
- return data;
-};
-
-export const useTodoAddMutation = () => {
- const queryClient = useQueryClient();
- return useMutation({
- mutationFn: addTodoFetcher,
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] });
- queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] });
- },
- });
-};
-
-// 수정 (Update Todo)
-const updateTodoFetcher = async updatedData => {
- const data = await Api.updateTodo(updatedData);
- return data;
-};
-
-export const useTodoUpdateMutation = () => {
- const queryClient = useQueryClient();
- return useMutation({
- mutationFn: updateTodoFetcher,
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] });
- queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] });
- },
- });
-};
-
-// 삭제 (Delete Todo)
-const deleteTodoFetcher = async todoId => {
- const data = await Api.deleteTodo(todoId.todoId);
- return data;
-};
-
-export const useTodoDeleteMutation = () => {
- const queryClient = useQueryClient();
- return useMutation({
- mutationFn: deleteTodoFetcher,
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] });
- queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] });
- },
- });
-};
diff --git a/hooks/api/useTodoMutations.ts b/hooks/api/useTodoMutations.ts
new file mode 100644
index 0000000..c7ccddb
--- /dev/null
+++ b/hooks/api/useTodoMutations.ts
@@ -0,0 +1,157 @@
+import { api as Api } from '@/utils/api';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { INBOX_QUERY_KEY } from './useInboxTodoQuery';
+import { TODO_QUERY_KEY } from './useTodoQuery';
+import { Todo } from '@/types/todo';
+
+const addTodoFetcher = async (todoData: Omit) => {
+ const data = await Api.addTodo(todoData);
+ return data;
+};
+
+export const useTodoAddMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: addTodoFetcher,
+ onMutate: async newTodo => {
+ await queryClient.cancelQueries({ queryKey: [TODO_QUERY_KEY] });
+ await queryClient.cancelQueries({ queryKey: [INBOX_QUERY_KEY] });
+
+ const previousTodos = queryClient.getQueryData([TODO_QUERY_KEY]);
+ const previousInbox = queryClient.getQueryData([INBOX_QUERY_KEY]);
+
+ const optimisticTodo: Todo = {
+ ...newTodo,
+ id: Date.now(),
+ children: [],
+ };
+
+ queryClient.setQueryData([TODO_QUERY_KEY], old => [
+ ...(old || []),
+ optimisticTodo,
+ ]);
+ queryClient.setQueryData([INBOX_QUERY_KEY], old => [
+ ...(old || []),
+ optimisticTodo,
+ ]);
+
+ return { previousTodos, previousInbox };
+ },
+ onError: (_err, _newTodo, context) => {
+ queryClient.setQueryData([TODO_QUERY_KEY], context?.previousTodos);
+ queryClient.setQueryData([INBOX_QUERY_KEY], context?.previousInbox);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] });
+ queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] });
+ },
+ });
+};
+
+const updateTodoFetcher = async (
+ updatedData: Partial & {
+ todoId: number;
+ patchRank?: { prevId: number | null };
+ },
+) => {
+ const data = await Api.updateTodo(updatedData);
+ return data;
+};
+
+export const useTodoUpdateMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: updateTodoFetcher,
+ onMutate: async updatedTodo => {
+ await queryClient.cancelQueries({ queryKey: [TODO_QUERY_KEY] });
+ await queryClient.cancelQueries({ queryKey: [INBOX_QUERY_KEY] });
+
+ const previousTodos = queryClient.getQueryData([TODO_QUERY_KEY]);
+ const previousInbox = queryClient.getQueryData([INBOX_QUERY_KEY]);
+
+ const updateTodoOrder = (old: Todo[] | undefined) => {
+ if (!old) return [];
+
+ if ('patchRank' in updatedTodo) {
+ const movedTodo = old.find(todo => todo.id === updatedTodo.todoId);
+ if (!movedTodo) return old;
+
+ const filteredTodos = old.filter(
+ todo => todo.id !== updatedTodo.todoId,
+ );
+
+ if (updatedTodo.patchRank.prevId === null) {
+ return [movedTodo, ...filteredTodos];
+ }
+
+ const targetIndex = filteredTodos.findIndex(
+ todo => todo.id === updatedTodo.patchRank!.prevId,
+ );
+
+ if (targetIndex === -1) return old;
+
+ return [
+ ...filteredTodos.slice(0, targetIndex + 1),
+ movedTodo,
+ ...filteredTodos.slice(targetIndex + 1),
+ ];
+ }
+
+ return old.map(todo => {
+ if (todo.id === updatedTodo.todoId) {
+ return { ...todo, ...updatedTodo };
+ }
+ return todo;
+ });
+ };
+
+ queryClient.setQueryData([TODO_QUERY_KEY], updateTodoOrder);
+ queryClient.setQueryData([INBOX_QUERY_KEY], updateTodoOrder);
+
+ return { previousTodos, previousInbox };
+ },
+ onError: (_err, _updatedTodo, context) => {
+ queryClient.setQueryData([TODO_QUERY_KEY], context?.previousTodos);
+ queryClient.setQueryData([INBOX_QUERY_KEY], context?.previousInbox);
+ },
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: [TODO_QUERY_KEY] });
+ queryClient.refetchQueries({ queryKey: [INBOX_QUERY_KEY] });
+ },
+ });
+};
+
+const deleteTodoFetcher = async ({ todoId }: { todoId: number }) => {
+ const data = await Api.deleteTodo(todoId);
+ return data;
+};
+
+export const useTodoDeleteMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: deleteTodoFetcher,
+ onMutate: async ({ todoId }) => {
+ await queryClient.cancelQueries({ queryKey: [TODO_QUERY_KEY] });
+ await queryClient.cancelQueries({ queryKey: [INBOX_QUERY_KEY] });
+
+ const previousTodos = queryClient.getQueryData([TODO_QUERY_KEY]);
+ const previousInbox = queryClient.getQueryData([INBOX_QUERY_KEY]);
+
+ const deleteTodo = (old: Todo[] | undefined) =>
+ old?.filter(todo => todo.id !== todoId) || [];
+
+ queryClient.setQueryData([TODO_QUERY_KEY], deleteTodo);
+ queryClient.setQueryData([INBOX_QUERY_KEY], deleteTodo);
+
+ return { previousTodos, previousInbox };
+ },
+ onError: (_err, _variables, context) => {
+ queryClient.setQueryData([TODO_QUERY_KEY], context?.previousTodos);
+ queryClient.setQueryData([INBOX_QUERY_KEY], context?.previousInbox);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] });
+ queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] });
+ },
+ });
+};
diff --git a/hooks/api/useTodoQuery.js b/hooks/api/useTodoQuery.js
index 63a760d..5081d57 100644
--- a/hooks/api/useTodoQuery.js
+++ b/hooks/api/useTodoQuery.js
@@ -1,4 +1,3 @@
-// useTodosQuery.js
import { api as Api } from '@/utils/api';
import { useQuery } from '@tanstack/react-query';
@@ -16,6 +15,9 @@ const useTodosQuery = userId => {
suspense: true,
refetchInterval: 60000,
refetchIntervalInBackground: true,
+ cacheTime: 180000,
+ staleTime: 30000,
+ keepPreviousData: true,
});
};
diff --git a/hooks/auth/useGoogleAuth.js b/hooks/auth/useGoogleAuth.js
index 0ccd86d..6ae5f4a 100644
--- a/hooks/auth/useGoogleAuth.js
+++ b/hooks/auth/useGoogleAuth.js
@@ -1,5 +1,4 @@
import { api as Api } from '@/utils/api';
-import messaging from '@react-native-firebase/messaging';
import * as Google from 'expo-auth-session/providers/google';
import { useEffect, useState } from 'react';
import { Platform } from 'react-native';
@@ -45,9 +44,6 @@ const useGoogleAuth = () => {
useEffect(() => {
getClientId();
handleLocalToken();
- (async () => {
- await messaging().requestPermission();
- })();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
diff --git a/hooks/auth/useLogin.js b/hooks/auth/useLogin.js
index 18c817d..3f5de38 100644
--- a/hooks/auth/useLogin.js
+++ b/hooks/auth/useLogin.js
@@ -17,7 +17,8 @@ const useLogin = () => {
const storage = useStorage();
const { deviceToken } = useDeviceToken();
const router = useRouter();
- const { setIsLoggedIn, setUserId, setAccessToken } = useContext(LoginContext);
+ const { setIsLoggedIn, setUserId, setAccessToken, setUserName } =
+ useContext(LoginContext);
const { mutate: addCategory } = useCategoryAddMutation();
const { t } = useTranslation();
@@ -42,6 +43,7 @@ const useLogin = () => {
const user = await Api.getUserInfo();
await setAsyncStorageLoginInfo(jwtTokenData, user);
setUserId(jwtTokenData.userId);
+ setUserName(jwtTokenData.email);
setIsLoggedIn(true);
if (jwtTokenData.isNew) {
handleAddCategory({ categoryName: t('views.categoryAddView.init') });
diff --git a/hooks/todo/useFilteredTodo.js b/hooks/todo/useFilteredTodo.js
index b6d9261..6d41cb3 100644
--- a/hooks/todo/useFilteredTodo.js
+++ b/hooks/todo/useFilteredTodo.js
@@ -1,17 +1,14 @@
-import { useEffect, useState } from 'react';
+import { useMemo } from 'react';
const useFilteredTodos = (todos, selectedCategory, selectedDate) => {
- const [filteredTodos, setFilteredTodos] = useState([]);
+ const filteredTodos = useMemo(() => {
+ if (!todos) return [];
- useEffect(() => {
- if (todos) {
- const filtered = todos.filter(
- todo =>
- todo.categoryId === selectedCategory &&
- todo.date === selectedDate.format('YYYY-MM-DD'),
- );
- setFilteredTodos(filtered);
- }
+ return todos.filter(
+ todo =>
+ todo.categoryId === selectedCategory &&
+ todo.date === selectedDate.format('YYYY-MM-DD'),
+ );
}, [todos, selectedCategory, selectedDate]);
return filteredTodos;
diff --git a/hooks/todo/useHandleDrag.js b/hooks/todo/useHandleDrag.js
index 8e28dec..d2bc410 100644
--- a/hooks/todo/useHandleDrag.js
+++ b/hooks/todo/useHandleDrag.js
@@ -5,9 +5,7 @@ const useHandleDrag = () => {
const handleDragEnd = ({ from, to, data: newData }) => {
if (!newData || newData.length === 0) return;
- if (from === to) {
- return;
- }
+ if (from === to) return;
let prevTodoId = null;
let nextTodoId = null;
@@ -22,7 +20,7 @@ const useHandleDrag = () => {
}
const updatedData = {
- todo_id: newData[to].id,
+ todoId: newData[to].id,
patchRank: {
prevId: prevTodoId,
nextId: nextTodoId,