Skip to content
31 changes: 15 additions & 16 deletions src/page/todo/myTodo/MyTodo.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const contentWrapper = style({
alignItems: 'flex-start',
width: '100%',
maxWidth: '128rem',
gap: '8.6rem', // 헤더-데이트피커 간격 86px
gap: '8.6rem',
});

export const datePickerSection = style({
Expand Down Expand Up @@ -124,19 +124,10 @@ export const checkMainContainer = style({
flexDirection: 'column',
alignItems: 'flex-start',
height: '67.2rem',
padding: '2.6rem 5rem 2.6rem',
gap: '1rem',
padding: '2.6rem',
alignSelf: 'stretch',
});

export const mainContentSection = style({
display: 'flex',
alignItems: 'flex-start',
gap: '1.9rem',
width: '100%',
height: '100%',
});

export const todoCheckArea = style({
display: 'flex',
flexDirection: 'column',
Expand All @@ -147,6 +138,7 @@ export const todoCheckArea = style({
height: '100%',
flexShrink: 0,
paddingRight: '1.9rem',
boxSizing: 'border-box',
});

export const selectorChipsContainer = style({
Expand All @@ -161,22 +153,25 @@ export const todoCheckContainer = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
width: '57.1rem',
width: '100%',
height: '53.8rem',
gap: '2.4rem',
alignSelf: 'stretch',
overflowY: 'auto',
boxSizing: 'border-box',
});

export const noScrollTodoCheckContainer = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
width: '100%',
height: '53.8rem',
gap: '2.4rem',
alignSelf: 'stretch',
overflowY: 'hidden',
overflow: 'hidden',
paddingRight: '1.9rem',
boxSizing: 'border-box',
});

export const todoCheckLine = style({
Expand Down Expand Up @@ -204,7 +199,7 @@ export const todoText = styleVariants({

export const emptyTodoBox = style({
display: 'flex',
width: '55.2rem',
width: '100%',
height: '53.8rem',
padding: '25.1rem 9.1rem',
justifyContent: 'center',
Expand All @@ -213,6 +208,7 @@ export const emptyTodoBox = style({
flexShrink: 0,
borderRadius: '0.8px',
background: colors.grey4,
boxSizing: 'border-box',
});

export const emptyTodoText = style({
Expand All @@ -223,8 +219,11 @@ export const emptyTodoText = style({

export const mandalartWithTodoSection = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
alignItems: 'flex-start',
justifyContent: 'flex-start',
gap: '3.7rem',
width: '100%',
height: '100%',
flex: 1,
});

Expand Down
165 changes: 81 additions & 84 deletions src/page/todo/myTodo/component/TodoCheckSection/TodoCheckSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const TodoCheckSection = ({
selectedCycle,
mandalartData,
onCycleClick,
onTodoClick,
onMandalartClick,
selectedParentId,
}: TodoCheckSectionProps) => {
Expand All @@ -49,37 +50,37 @@ const TodoCheckSection = ({
const [localSubGoals, setLocalSubGoals] = useState<TodoItemTypes[]>([]);

useEffect(() => {
const subGoals: TodoItemTypes[] = (subGoalResponse?.data?.subGoals ?? []).map((goal) => ({
const apiSubGoals = (subGoalResponse?.data?.subGoals ?? []).map((goal) => ({
id: goal.id,
title: goal.title,
content: goal.title,
cycle: goal.cycle,
isCompleted: goal.isCompleted,
content: goal.title,
}));
setLocalSubGoals(subGoals);

setLocalSubGoals(apiSubGoals);
}, [subGoalResponse]);

const updateLocalSubGoalCompletion = (id: TodoItemTypes['id'], nextValue: boolean) => {
setLocalSubGoals((prev) =>
prev.map((goal) => (goal.id === id ? { ...goal, isCompleted: nextValue } : goal)),
);
};

const checkSubGoalMutation = useCheckSubGoal();
const uncheckSubGoalMutation = useUncheckSubGoal();

const handleTodoClick = (item: TodoItemTypes) => {
const today = new Date().toISOString().split('T')[0];
const originalCompleted = item.isCompleted;
const originalCompleted = Boolean(item.isCompleted);
const nextCompleted = !originalCompleted;

setLocalSubGoals((prev) =>
prev.map((goal) =>
goal.id === item.id ? { ...goal, isCompleted: !goal.isCompleted } : goal,
),
);
updateLocalSubGoalCompletion(item.id, nextCompleted);
onTodoClick({ ...item, isCompleted: nextCompleted });

if (originalCompleted === true) {
if (originalCompleted) {
uncheckSubGoalMutation.mutate(Number(item.id), {
onError: () => {
setLocalSubGoals((prev) =>
prev.map((goal) =>
goal.id === item.id ? { ...goal, isCompleted: originalCompleted } : goal,
),
);
updateLocalSubGoalCompletion(item.id, originalCompleted);
},
});
} else {
Expand All @@ -90,17 +91,32 @@ const TodoCheckSection = ({
},
{
onError: () => {
setLocalSubGoals((prev) =>
prev.map((goal) =>
goal.id === item.id ? { ...goal, isCompleted: originalCompleted } : goal,
),
);
updateLocalSubGoalCompletion(item.id, originalCompleted);
},
},
);
}
};

const hasTodos = localSubGoals.length > 0;
const todoContainerClass = hasTodos
? styles.todoCheckContainer
: styles.noScrollTodoCheckContainer;

const renderEmptyState = () => (
<div className={styles.emptyTodoBox}>
<span className={styles.emptyTodoText}>해당하는 할 일이 없어요</span>
</div>
);

const renderTodoItems = () =>
localSubGoals.map((todo) => (
<div key={todo.id} className={styles.todoCheckLine}>
<CycleChip type="display" value={todo.cycle as CycleType} />
<TodoBox type="todo" items={[todo]} onItemClick={handleTodoClick} />
</div>
));

return (
<section className={styles.checkSection}>
<header className={styles.checkTextWrapper}>
Expand All @@ -109,69 +125,50 @@ const TodoCheckSection = ({
</header>

<section className={styles.checkMainContainer}>
<div className={styles.mainContentSection}>
<div className={styles.mandalartWithTodoSection}>
<Mandalart
type="TODO_MAIN"
data={{
id: 0,
position: 4,
title: mandalartData.title || mandalartData.mainGoal,
subGoals: Array.isArray(coreGoalsData?.data?.coreGoals)
? coreGoalsData.data.coreGoals.map(
(
goal: { title: string; position: number; subGoals?: unknown[] },
idx: number,
) => ({
id: idx < 4 ? idx : idx + 1,
title: goal.title,
position: goal.position,
subGoals: goal.subGoals ?? [],
}),
)
: [],
}}
onGoalClick={(position) => {
const coreGoal = coreGoalsData?.data?.coreGoals.find(
(goal) => goal.position === position,
);
const parentId = coreGoal?.id;
onMandalartClick(selectedParentId === parentId ? undefined : parentId);
}}
/>
<div className={styles.todoCheckArea}>
<div className={styles.selectorChipsContainer}>
{CYCLE_LIST.map((cycle) => (
<CycleChip
key={cycle}
type="selector"
value={cycle}
selected={selectedCycle === cycle}
onClick={onCycleClick}
/>
))}
</div>

<div
className={
localSubGoals.length === 0
? styles.noScrollTodoCheckContainer
: styles.todoCheckContainer
}
>
{localSubGoals.length === 0 ? (
<div className={styles.emptyTodoBox}>
<span className={styles.emptyTodoText}>해당하는 할 일이 없어요</span>
</div>
) : (
localSubGoals.map((todo) => (
<div key={todo.id} className={styles.todoCheckLine}>
<CycleChip type="display" value={todo.cycle as CycleType} />
<TodoBox type="todo" items={[todo]} onItemClick={handleTodoClick} />
</div>
))
)}
</div>
<div className={styles.mandalartWithTodoSection}>
<Mandalart
type="TODO_MAIN"
data={{
id: 0,
position: 4,
title: mandalartData.title || mandalartData.mainGoal,
subGoals: Array.isArray(coreGoalsData?.data?.coreGoals)
? coreGoalsData.data.coreGoals.map(
(
goal: { title: string; position: number; subGoals?: unknown[] },
idx: number,
) => ({
id: idx < 4 ? idx : idx + 1,
title: goal.title,
position: goal.position,
subGoals: goal.subGoals ?? [],
}),
)
: [],
}}
onGoalClick={(position) => {
const coreGoal = coreGoalsData?.data?.coreGoals.find(
(goal) => goal.position === position,
);
const parentId = coreGoal?.id;
onMandalartClick(selectedParentId === parentId ? undefined : parentId);
}}
/>
<div className={styles.todoCheckArea}>
<div className={styles.selectorChipsContainer}>
{CYCLE_LIST.map((cycle) => (
<CycleChip
key={cycle}
type="selector"
value={cycle}
selected={selectedCycle === cycle}
onClick={onCycleClick}
/>
))}
</div>

<div className={todoContainerClass}>
{hasTodos ? renderTodoItems() : renderEmptyState()}
</div>
</div>
</div>
Expand Down
22 changes: 13 additions & 9 deletions src/page/todo/myTodo/hook/useMyTodo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
import type { CycleType } from '../constant/mock';

import type { TodoItemTypes } from '@/page/todo/myTodo/component/TodoBox/TodoBox.types';
import { createDate, formatDateDot } from '@/common/util/format';
import { formatDateDot } from '@/common/util/format';
import { useGetRecommendation } from '@/api/domain/myTodo/hook/useGetRecommendation';
import { usePostRecommendation } from '@/api/domain/myTodo/hook/usePostRecommendation';
import { useDeleteRecommendation } from '@/api/domain/myTodo/hook/useDeleteRecommendation';
Expand Down Expand Up @@ -31,19 +31,20 @@ interface UseMyTodoProps {
initialMyTodos?: TodoItemTypes[];
}

const MIN_DATE = createDate(2025, 1, 1);
const MAX_DATE = createDate(2025, 1, 31);
export const useMyTodo = ({ initialDate }: UseMyTodoProps = {}) => {
const defaultDate = initialDate ?? new Date();
const startOfMonth = new Date(defaultDate.getFullYear(), defaultDate.getMonth(), 1);
const endOfMonth = new Date(defaultDate.getFullYear(), defaultDate.getMonth() + 1, 0);

export const useMyTodo = ({ initialDate = createDate(2025, 7, 18) }: UseMyTodoProps = {}) => {
const MANDALART_ID = useMandalartId();
const [currentDate, setCurrentDate] = useState(initialDate);
const [currentDate, setCurrentDate] = useState(defaultDate);
const [selectedCycle, setSelectedCycle] = useState<CycleType>();
const [selectedParentId, setSelectedParentId] = useState<number>();
const [todos, setTodos] = useState<TodoItemTypes[]>([]);
const [recommendTodos, setRecommendTodos] = useState<TodoItemTypes[]>([]);

const mandalartId = useMandalartId();
const formattedDate = formatDateDot(currentDate);
const { data: recommendationData, refetch } = useGetRecommendation(MANDALART_ID, formattedDate);
const { data: recommendationData, refetch } = useGetRecommendation(mandalartId, formattedDate);
const { mutate: completeTodo } = usePostRecommendation();
const { mutate: deleteTodo } = useDeleteRecommendation();

Expand All @@ -65,10 +66,13 @@ export const useMyTodo = ({ initialDate = createDate(2025, 7, 18) }: UseMyTodoPr
setTodos(mockSubGoals);
}, []);

const hasPreviousDate = currentDate > MIN_DATE;
const hasNextDate = currentDate < MAX_DATE;
const hasPreviousDate = currentDate > startOfMonth;
const hasNextDate = currentDate < endOfMonth;

const handleDateChange = (newDate: Date) => {
if (newDate < startOfMonth || newDate > endOfMonth) {
return;
}
setCurrentDate(newDate);
};

Expand Down
Loading