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 (
- {/* ✅ 왼쪽 사이드바 - 목록 + 태그 */}
+ {/* ✅ 왼쪽 사이드바 */}
📂 목록
@@ -87,6 +106,7 @@ const TaskPage = () => {
onClick={() => {
setSelectedFilter(filter.value);
setSelectedTag(null);
+ setCurrentPage(1); // ✅ 필터 변경 시 페이지 초기화
}}
className={`w-full text-left px-2 py-1 rounded hover:bg-gray-100 ${
selectedFilter === filter.value && !selectedTag
@@ -107,6 +127,7 @@ const TaskPage = () => {
onClick={() => {
setSelectedTag(tag);
setSelectedFilter(null);
+ setCurrentPage(1); // ✅ 태그 선택 시 페이지 초기화
}}
className={`w-full text-left px-2 py-1 rounded hover:bg-gray-100 ${
selectedTag === tag ? "bg-blue-100 font-semibold" : ""
@@ -118,7 +139,7 @@ const TaskPage = () => {
- {/* 가운데 메인 영역 */}
+ {/* 가운데 메인 */}
@@ -131,15 +152,62 @@ const TaskPage = () => {
{
setSelectedTag(tag);
setSelectedFilter(null);
+ setCurrentPage(1);
}}
onDelete={deleteTask}
/>
+
+ {/* ✅ 페이지네이션 개선 */}
+
+ {/* 이전 버튼 */}
+ setCurrentPage((prev) => Math.max(prev - 1, 1))}
+ disabled={currentPage === 1}
+ className={`px-3 py-1 rounded ${
+ currentPage === 1
+ ? "bg-gray-100 text-gray-400 cursor-not-allowed"
+ : "bg-gray-200 text-gray-700 hover:bg-gray-300"
+ }`}
+ >
+ <
+
+
+ {/* 페이지 번호 */}
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
+ setCurrentPage(page)}
+ className={`px-3 py-1 rounded ${
+ currentPage === page
+ ? "bg-blue-500 text-white"
+ : "bg-gray-200 text-gray-700 hover:bg-gray-300"
+ }`}
+ >
+ {page}
+
+ ))}
+
+ {/* 다음 버튼 */}
+
+ setCurrentPage((prev) => Math.min(prev + 1, totalPages))
+ }
+ disabled={currentPage === totalPages}
+ className={`px-3 py-1 rounded ${
+ currentPage === totalPages
+ ? "bg-gray-100 text-gray-400 cursor-not-allowed"
+ : "bg-gray-200 text-gray-700 hover:bg-gray-300"
+ }`}
+ >
+ >
+
+
{/* 오른쪽 상세 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 (
+
+ {/* ✅ 왼쪽 사이드바 - 목록 + 태그 */}
+
+
+
📂 목록
+ {FILTERS.map((filter) => (
+ {
+ setSelectedFilter(filter.value);
+ setSelectedTag(null);
+ }}
+ className={`w-full text-left px-2 py-1 rounded hover:bg-gray-100 ${
+ selectedFilter === filter.value && !selectedTag
+ ? "bg-blue-100 font-semibold"
+ : ""
+ }`}
+ >
+ {filter.label}
+
+ ))}
+
+
+
+
🏷 태그
+ {allTags.map((tag) => (
+ {
+ setSelectedTag(tag);
+ setSelectedFilter(null);
+ }}
+ className={`w-full text-left px-2 py-1 rounded hover:bg-gray-100 ${
+ selectedTag === tag ? "bg-blue-100 font-semibold" : ""
+ }`}
+ >
+ #{tag}
+
+ ))}
+
+
+
+ {/* 가운데 메인 영역 */}
+
+
+
+
+ 📋{" "}
+ {(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