↓↓↓↓이 프로젝트의 전체 코드는 GitHub에서 확인하실 수 있습니다.↓↓↓↓
https://github.com/GangHyun95/calendar-todo
GitHub - GangHyun95/calendar-todo: with typescript
with typescript. Contribute to GangHyun95/calendar-todo development by creating an account on GitHub.
github.com
이번 포스트에서는 To-Do 리스트 앱에 수정, 삭제 기능을 추가하고, 체크박스 상태에 따라 완료 여부를 관리하는 방법을 설명하겠습니다. 또한, To-Do 항목의 완료 상태에 따라 캘린더에서 시각적으로 표시하는 방법도 다룹니다.
모달 컴포넌트 설계
ModalContent 컴포넌트는 다음 props를 받습니다:
- onClose: 모달을 닫는 함수
- mode: 현재 모달의 동작 모드 (add, mod, del)
- todoId: 수정 또는 삭제할 To-Do 항목의 ID (선택적)
interface ModalContentProps {
onClose: () => void;
mode: "add" | "mod" | "del";
todoId?: string | null;
}
수정 및 삭제 로직 구현
수정 로직:
- mode가 "mod"인 경우 map을 사용하여 기존 Todo를 찾아 텍스트와 날짜만 업데이트합니다.
- oldDate와 taskDate가 다를 경우, 기존 날짜에서 해당 Todo를 제거하고 새 날짜에 추가합니다.
- mode가 "add"인 경우에는 단순히 새로운 날짜에 Todo를 추가합니다.
const handleAddOrUpdateTodo = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const newTodo: TodoItem = {
id: todoId || Date.now().toString(),
text: taskText,
date: taskDate,
completed: false,
};
const updatedTodos = { ...todos };
let oldDate = '';
if (mode === "mod" && todoId) {
// 기존 To-Do 수정
Object.keys(updatedTodos).forEach((date) => {
updatedTodos[date] = updatedTodos[date].map((todo) => {
if (todo.id === todoId) {
oldDate = date;
return { ...todo, text: taskText, date: taskDate, completed: todo.completed };
}
return todo;
});
});
// 날짜가 변경되었다면, 기존 날짜에서 제거하고 새로운 날짜에 추가
if (oldDate !== taskDate) {
updatedTodos[oldDate] = updatedTodos[oldDate].filter(todo => todo.id !== todoId);
if (updatedTodos[oldDate].length === 0) {
delete updatedTodos[oldDate];
}
updatedTodos[taskDate] = [...(updatedTodos[taskDate] || []), newTodo];
}
} else {
updatedTodos[taskDate] = [...(updatedTodos[taskDate] || []), newTodo];
}
setTodos(updatedTodos);
setCurrentDate(new Date(taskDate));
onClose();
};
삭제 로직:
선택된 To-Do를 삭제하고 상태 및 로컬 스토리지를 업데이트합니다.
if (mode === "del" && todoId) {
Object.keys(todos).forEach((date) => {
todos[date] = todos[date].filter((todo) => todo.id !== todoId);
if (todos[date].length === 0) {
delete todos[date];
}
});
setTodos({ ...todos });
onClose();
return;
}
체크박스 토글 시 completed 상태 업데이트
Todo 컴포넌트에서는 체크박스를 클릭하여 To-Do 항목의 completed 상태를 토글할 수 있습니다. 이때 상태와 로컬 스토리지 모두 업데이트됩니다.
completed가 true인 경우, 텍스트에 별도의 클래스를 추가하여 스타일링을 적용합니다.
import React from 'react';
import { useTodos } from "../../context/TodoContext";
import styles from "./Todo.module.css";
import { IoTrash } from "react-icons/io5";
import { HiOutlinePencilAlt } from "react-icons/hi";
interface TodoProps {
id: string;
text: string;
completed: boolean;
openModal: (id: string, mode: "mod" | "del") => void;
}
export default function Todo({ id, text, completed, openModal }: TodoProps) {
const { todos, setTodos } = useTodos();
const toggleCompleted = (id: string) => {
const updatedTodos = { ...todos };
for (const date in updatedTodos) {
updatedTodos[date] = updatedTodos[date].map(todo => {
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
}
setTodos(updatedTodos);
};
return (
<li className={styles.todo}>
<input
type="checkbox"
className={styles.checkbox}
checked={completed}
onChange={() => toggleCompleted(id)}
id={id}
/>
<label
htmlFor={id}
className={`${styles.text} ${completed ? styles.completed : ''}`}
>
{text}
</label>
<div className={styles.wrap}>
<button className={styles.button} onClick={() => openModal(id, "mod")}>
<HiOutlinePencilAlt />
</button>
<button className={styles.button} onClick={() => openModal(id, "del")}>
<IoTrash />
</button>
</div>
</li>
);
}
캘린더에서 To-Do 상태 표시
캘린더에서는 특정 날짜에 할 일이 있는 경우와 완료된 경우를 시각적으로 구분하였습니다.
이를 위해 getTodoStatusByDate 함수를 사용해 특정 날짜의 To-Do 상태를 파악하고, 그에 따라 캘린더에 클래스를 부여합니다.
import styles from "./CalendarContent.module.css";
import { useDate } from "../../context/DateContext";
import { useTodos } from "../../context/TodoContext";
function getMonthDays(year: number, month: number) {
const days: (number | null)[] = [];
const date = new Date(year, month, 1);
const firstDayOfWeek = date.getDay();
for (let i = 0; i < firstDayOfWeek; i++) {
days.push(null);
}
while (date.getMonth() === month) {
days.push(date.getDate());
date.setDate(date.getDate() + 1);
}
return days;
}
export default function CalendarContent() {
const { currentDate, setCurrentDate } = useDate();
const { todos } = useTodos();
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
const days = getMonthDays(year, month);
const weekdayHeaders = ["일", "월", "화", "수", "목", "금", "토"];
const handleDateClick = (day: number | null) => {
if (day !== null) {
const newDate = new Date(year, month, day);
setCurrentDate(newDate);
}
};
const getTodoStatus = (
year: number,
month: number,
day: number
): string => {
const dateKey = `${year}-${String(month + 1).padStart(2, "0")}-${String(
day
).padStart(2, "0")}`;
const dayTodos = todos[dateKey] || [];
const allDone = dayTodos.every((todo) => todo.completed);
const anyTodo = dayTodos.length > 0;
if (anyTodo && allDone) {
return "done";
} else if (anyTodo) {
return "doing";
}
return "";
};
return (
<section className={styles["calendar-content"]}>
<div className={styles["calendar-grid"]}>
{weekdayHeaders.map((header) => (
<div key={header} className={styles["calendar-header"]}>
{header}
</div>
))}
{days.map((day, index) => {
const dayStatus = day
? getTodoStatus(year, month, day)
: "";
return (
<div
key={index}
className={`${styles["calendar-day"]} ${
day === currentDate.getDate()
? styles["selected"]
: ""
} ${styles[dayStatus]}`}
onClick={() => handleDateClick(day)}
>
{day || ""}
</div>
);
})}
</div>
</section>
);
}
padStart를 사용한 이유
padStart 메소드는 날짜 형식이 항상 두 자리가 되도록 보장해 줍니다.
예를 들어, 2024년 8월 1일을 '2024-08-01'로 표현해야 하는데, padStart를 사용하지 않으면 '2024-8-1'처럼 불규칙한 형식이 될 수 있습니다. 일정한 형식을 유지하기 위해 padStart를 사용하여 한 자릿수의 달이나 일을 두 자릿수로 채웁니다.
'React > Calendar Todo' 카테고리의 다른 글
React로 달력형 To-Do List 만들기 - 마무리 (with TypeScript) (0) | 2024.08.20 |
---|---|
React로 달력형 To-Do List 만들기 - 4 (with TypeScript) (0) | 2024.08.16 |
React로 달력형 To-Do List 만들기 - 3 (with TypeScript) (0) | 2024.08.15 |
React로 달력형 To-Do List 만들기 - 2 (with TypeScript) (0) | 2024.08.14 |
React로 달력형 To-Do List 만들기 - 1 (with TypeScript) (0) | 2024.08.13 |