From c262b1c863d45364656e5c951bdd88a289f4d62c Mon Sep 17 00:00:00 2001 From: icurfer Date: Thu, 22 May 2025 00:04:21 +0900 Subject: [PATCH] =?UTF-8?q?todo=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B0=9C=EC=84=A0,?= =?UTF-8?q?=20=EB=AF=B8=EC=99=84=EB=A3=8C=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=95=84=EB=9E=98=EB=A1=9C=20=EB=82=B4=EB=A0=A4=EA=B0=80?= =?UTF-8?q?=EB=8A=94=EC=84=A4=EC=A0=95,=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/TaskPage.js | 112 ++++++++++++++++----- src/pages/_stay_TaskPage.js | 192 ++++++++++++++++++++++++++++++++++++ version | 2 +- 3 files changed, 283 insertions(+), 23 deletions(-) create mode 100644 src/pages/_stay_TaskPage.js diff --git a/src/pages/TaskPage.js b/src/pages/TaskPage.js index 33f6648..a0bc720 100644 --- a/src/pages/TaskPage.js +++ b/src/pages/TaskPage.js @@ -8,16 +8,19 @@ import todoApi from "../api/todoApi"; const FILTERS = [ { label: "오늘 할 일", value: "today" }, { label: "중요", value: "important" }, - { label: "계획된 일정", value: "scheduled" }, + { label: "미완료 작업", value: "incomplete" }, + { label: "완료된 작업", value: "completed" }, // ✅ 변경 { label: "전체", value: "all" }, - { label: "완료된 작업", value: "completed" }, // ✅ 추가됨 ]; +const ITEMS_PER_PAGE = 5; // ✅ 페이지당 항목 수 + const TaskPage = () => { const [tasks, setTasks] = useState([]); - const [selectedFilter, setSelectedFilter] = useState("all"); const [selectedTag, setSelectedTag] = useState(null); const [selectedTask, setSelectedTask] = useState(null); + const [selectedFilter, setSelectedFilter] = useState("all"); + const [currentPage, setCurrentPage] = useState(1); // ✅ 현재 페이지 const fetchTasks = useCallback(async () => { try { @@ -34,20 +37,36 @@ const TaskPage = () => { const closeDetail = () => setSelectedTask(null); - const filteredTasks = tasks.filter((task) => { - if (selectedTag) return task.tags?.includes(selectedTag); - if (selectedFilter === "today") { - const today = new Date().toISOString().split("T")[0]; - return task.due_date === today; - } else if (selectedFilter === "important") { - return task.tags?.includes("중요"); - } else if (selectedFilter === "scheduled") { - return !!task.due_date; - } else if (selectedFilter === "completed") { - return task.is_completed; - } - return true; // 전체 - }); + const filteredTasks = tasks + .filter((task) => { + if (selectedTag) return task.tags?.includes(selectedTag); + if (selectedFilter === "today") { + const today = new Date().toISOString().split("T")[0]; + return task.due_date === today; + } else if (selectedFilter === "important") { + return task.tags?.includes("중요"); + } else if (selectedFilter === "incomplete") { + return !task.is_completed; + } else if (selectedFilter === "completed") { + return task.is_completed; + } + return true; + }) + .sort((a, b) => { + if (a.is_completed && !b.is_completed) return 1; + if (!a.is_completed && b.is_completed) return -1; + if (!a.is_completed && !b.is_completed) { + return (a.due_date || "").localeCompare(b.due_date || ""); + } + return 0; + }); + + // ✅ 페이지네이션 적용 + const totalPages = Math.ceil(filteredTasks.length / ITEMS_PER_PAGE); + const paginatedTasks = filteredTasks.slice( + (currentPage - 1) * ITEMS_PER_PAGE, + currentPage * ITEMS_PER_PAGE + ); const allTags = [...new Set(tasks.flatMap((task) => task.tags || []))]; @@ -77,7 +96,7 @@ const TaskPage = () => { return (
- {/* ✅ 왼쪽 사이드바 - 목록 + 태그 */} + {/* ✅ 왼쪽 사이드바 */} - {/* 가운데 메인 영역 */} + {/* 가운데 메인 */}
@@ -131,15 +152,62 @@ const TaskPage = () => {
{ setSelectedTag(tag); setSelectedFilter(null); + setCurrentPage(1); }} onDelete={deleteTask} /> + + {/* ✅ 페이지네이션 개선 */} +
+ {/* 이전 버튼 */} + + + {/* 페이지 번호 */} + {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( + + ))} + + {/* 다음 버튼 */} + +
{/* 오른쪽 상세 Drawer */} @@ -159,8 +227,8 @@ const TaskPage = () => { { - fetchTasks(); // 목록 갱신 - setSelectedTask(null); // 상세 창 닫기 (필요에 따라 유지 가능) + fetchTasks(); + setSelectedTask(null); }} />
diff --git a/src/pages/_stay_TaskPage.js b/src/pages/_stay_TaskPage.js new file mode 100644 index 0000000..84693cc --- /dev/null +++ b/src/pages/_stay_TaskPage.js @@ -0,0 +1,192 @@ +// ✅ src/pages/TaskPage.js +import React, { useEffect, useState, useCallback } from "react"; +import TodoForm from "../components/Todo/TodoForm"; +import TodoList from "../components/Todo/TodoList"; +import TodoDetail from "../components/Todo/TodoDetail"; +import todoApi from "../api/todoApi"; + +const FILTERS = [ + { label: "오늘 할 일", value: "today" }, + { label: "중요", value: "important" }, + { label: "미완료 작업", value: "incomplete" }, + { label: "완료된 작업", value: "completed" }, // ✅ 추가됨 + { label: "전체", value: "all" }, +]; + +const TaskPage = () => { + const [tasks, setTasks] = useState([]); + const [selectedTag, setSelectedTag] = useState(null); + const [selectedTask, setSelectedTask] = useState(null); + const [selectedFilter, setSelectedFilter] = useState("all"); + + const fetchTasks = useCallback(async () => { + try { + const res = await todoApi.get("/api/todo/tasks/"); + setTasks(res.data); + } catch (err) { + console.error("목록 불러오기 실패:", err); + } + }, []); + + useEffect(() => { + fetchTasks(); + }, [fetchTasks]); + + const closeDetail = () => setSelectedTask(null); + + const filteredTasks = tasks + .filter((task) => { + if (selectedTag) return task.tags?.includes(selectedTag); + if (selectedFilter === "today") { + const today = new Date().toISOString().split("T")[0]; + return task.due_date === today; + } else if (selectedFilter === "important") { + return task.tags?.includes("중요"); + } else if (selectedFilter === "incomplete") { + return !task.is_completed; // ✅ is_completed가 false인 경우만 + } else if (selectedFilter === "completed") { + return task.is_completed; + } + return true; // 전체 + }) + .sort((a, b) => { + // 완료된 작업은 아래로, 미완료 작업은 due_date 기준 오름차순 + if (a.is_completed && !b.is_completed) return 1; + if (!a.is_completed && b.is_completed) return -1; + + if (!a.is_completed && !b.is_completed) { + return (a.due_date || "").localeCompare(b.due_date || ""); + } + + return 0; // 둘 다 완료된 경우 순서 유지 + }); + + const allTags = [...new Set(tasks.flatMap((task) => task.tags || []))]; + + const toggleComplete = async (taskId, is_completed) => { + try { + await todoApi.patch(`/api/todo/tasks/${taskId}/`, { + is_completed: !is_completed, + }); + fetchTasks(); + } catch (err) { + console.error("완료 상태 변경 실패:", err); + } + }; + + const deleteTask = async (taskId) => { + if (!window.confirm("정말로 이 작업을 삭제하시겠습니까?")) return; + + try { + await todoApi.delete(`/api/todo/tasks/${taskId}/`); + if (selectedTask?.id === taskId) setSelectedTask(null); + fetchTasks(); + } catch (err) { + console.error("삭제 실패:", err); + alert("삭제에 실패했습니다."); + } + }; + + return ( +
+ {/* ✅ 왼쪽 사이드바 - 목록 + 태그 */} + + + {/* 가운데 메인 영역 */} +
+ +
+

+ 📋{" "} + {(selectedTag && `태그: ${selectedTag}`) || + FILTERS.find((f) => f.value === selectedFilter)?.label || + "할 일"} +

+
+ + { + setSelectedTag(tag); + setSelectedFilter(null); + }} + onDelete={deleteTask} + /> +
+ + {/* 오른쪽 상세 Drawer */} +
+ + {selectedTask && ( +
+ { + fetchTasks(); // 목록 갱신 + setSelectedTask(null); // 상세 창 닫기 (필요에 따라 유지 가능) + }} + /> +
+ )} +
+ + {selectedTask && ( +
+ )} +
+ ); +}; + +export default TaskPage; diff --git a/version b/version index f64956b..1111c9c 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.0.13-rc1 \ No newline at end of file +0.0.14 \ No newline at end of file