Files
msa-fe/src/pages/TaskPage.js
icurfer 8cbfe4a864
All checks were successful
Build And Test / build-and-push (push) Successful in 1m36s
Add todo 리스트 관리 기능 구현
2025-05-18 23:38:52 +09:00

181 lines
5.6 KiB
JavaScript

// ✅ 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: "scheduled" },
{ label: "전체", value: "all" },
{ label: "완료된 작업", value: "completed" }, // ✅ 추가됨
];
const TaskPage = () => {
const [tasks, setTasks] = useState([]);
const [selectedFilter, setSelectedFilter] = useState("all");
const [selectedTag, setSelectedTag] = useState(null);
const [selectedTask, setSelectedTask] = useState(null);
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 === "scheduled") {
return !!task.due_date;
} else if (selectedFilter === "completed") {
return task.is_completed;
}
return true; // 전체
});
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 (
<div className="flex h-screen bg-gray-50">
{/* ✅ 왼쪽 사이드바 - 목록 + 태그 */}
<aside className="w-60 border-r border-gray-200 p-4 space-y-4 bg-white overflow-y-auto">
<div>
<h2 className="text-lg font-bold mb-2">📂 목록</h2>
{FILTERS.map((filter) => (
<button
key={filter.value}
onClick={() => {
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}
</button>
))}
</div>
<div>
<h2 className="text-md font-semibold mt-6 mb-2">🏷 태그</h2>
{allTags.map((tag) => (
<button
key={tag}
onClick={() => {
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}
</button>
))}
</div>
</aside>
{/* 가운데 메인 영역 */}
<main className="flex-1 border-r border-gray-200 p-4 overflow-y-auto">
<TodoForm onTaskCreated={fetchTasks} />
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">
📋{" "}
{(selectedTag && `태그: ${selectedTag}`) ||
FILTERS.find((f) => f.value === selectedFilter)?.label ||
"할 일"}
</h2>
</div>
<TodoList
tasks={filteredTasks}
onSelectTask={setSelectedTask}
onToggleComplete={toggleComplete}
onTagClick={(tag) => {
setSelectedTag(tag);
setSelectedFilter(null);
}}
onDelete={deleteTask}
/>
</main>
{/* 오른쪽 상세 Drawer */}
<section
className={`fixed top-0 right-0 h-full w-full md:w-1/2 lg:w-2/5 xl:w-1/3 bg-white shadow-lg border-l border-gray-300 transform transition-transform duration-300 ease-in-out z-50 ${
selectedTask ? "translate-x-0" : "translate-x-full"
}`}
>
<button
onClick={closeDetail}
className="absolute top-4 right-4 text-gray-600 hover:text-red-500 text-2xl font-bold"
>
&times;
</button>
{selectedTask && (
<div className="p-6">
<TodoDetail
task={selectedTask}
onUpdated={() => {
fetchTasks(); // 목록 갱신
setSelectedTask(null); // 상세 창 닫기 (필요에 따라 유지 가능)
}}
/>
</div>
)}
</section>
{selectedTask && (
<div
className="fixed inset-0 bg-black bg-opacity-30 z-40"
onClick={closeDetail}
/>
)}
</div>
);
};
export default TaskPage;