"use client"; import React, { useState, useEffect } from "react"; import { Plus, Check, X, Clock, AlertCircle, GripVertical, ChevronDown } from "lucide-react"; interface TodoItem { id: string; title: string; description?: string; priority: "urgent" | "high" | "normal" | "low"; status: "pending" | "in_progress" | "completed"; assignedTo?: string; dueDate?: string; createdAt: string; updatedAt: string; completedAt?: string; isUrgent: boolean; order: number; } interface TodoStats { total: number; pending: number; inProgress: number; completed: number; urgent: number; overdue: number; } export default function TodoWidget() { const [todos, setTodos] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState<"all" | "pending" | "in_progress" | "completed">("all"); const [showAddForm, setShowAddForm] = useState(false); const [newTodo, setNewTodo] = useState({ title: "", description: "", priority: "normal" as TodoItem["priority"], isUrgent: false, dueDate: "", assignedTo: "", }); useEffect(() => { fetchTodos(); const interval = setInterval(fetchTodos, 30000); // 30초마다 갱신 return () => clearInterval(interval); }, [filter]); const fetchTodos = async () => { try { const token = localStorage.getItem("authToken"); const filterParam = filter !== "all" ? `?status=${filter}` : ""; const response = await fetch(`http://localhost:9771/api/todos${filterParam}`, { headers: { Authorization: `Bearer ${token}`, }, }); if (response.ok) { const result = await response.json(); setTodos(result.data || []); setStats(result.stats); } } catch (error) { // console.error("To-Do 로딩 오류:", error); } finally { setLoading(false); } }; const handleAddTodo = async () => { if (!newTodo.title.trim()) return; try { const token = localStorage.getItem("authToken"); const response = await fetch("http://localhost:9771/api/todos", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(newTodo), }); if (response.ok) { setNewTodo({ title: "", description: "", priority: "normal", isUrgent: false, dueDate: "", assignedTo: "", }); setShowAddForm(false); fetchTodos(); } } catch (error) { // console.error("To-Do 추가 오류:", error); } }; const handleUpdateStatus = async (id: string, status: TodoItem["status"]) => { try { const token = localStorage.getItem("authToken"); const response = await fetch(`http://localhost:9771/api/todos/${id}`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ status }), }); if (response.ok) { fetchTodos(); } } catch (error) { // console.error("상태 업데이트 오류:", error); } }; const handleDelete = async (id: string) => { if (!confirm("이 To-Do를 삭제하시겠습니까?")) return; try { const token = localStorage.getItem("authToken"); const response = await fetch(`http://localhost:9771/api/todos/${id}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}`, }, }); if (response.ok) { fetchTodos(); } } catch (error) { // console.error("To-Do 삭제 오류:", error); } }; const getPriorityColor = (priority: TodoItem["priority"]) => { switch (priority) { case "urgent": return "bg-red-100 text-red-700 border-red-300"; case "high": return "bg-orange-100 text-orange-700 border-orange-300"; case "normal": return "bg-blue-100 text-blue-700 border-blue-300"; case "low": return "bg-gray-100 text-gray-700 border-gray-300"; } }; const getPriorityIcon = (priority: TodoItem["priority"]) => { switch (priority) { case "urgent": return "🔴"; case "high": return "🟠"; case "normal": return "🟡"; case "low": return "🟢"; } }; const getTimeRemaining = (dueDate: string) => { const now = new Date(); const due = new Date(dueDate); const diff = due.getTime() - now.getTime(); const hours = Math.floor(diff / (1000 * 60 * 60)); const days = Math.floor(hours / 24); if (diff < 0) return "⏰ 기한 초과"; if (days > 0) return `📅 ${days}일 남음`; if (hours > 0) return `⏱️ ${hours}시간 남음`; return "⚠️ 오늘 마감"; }; if (loading) { return (
로딩 중...
); } return (
{/* 헤더 */}

✅ To-Do / 긴급 지시

{/* 통계 */} {stats && (
{stats.pending}
대기
{stats.inProgress}
진행중
{stats.urgent}
긴급
{stats.overdue}
지연
)} {/* 필터 */}
{(["all", "pending", "in_progress", "completed"] as const).map((f) => ( ))}
{/* 추가 폼 */} {showAddForm && (
setNewTodo({ ...newTodo, title: e.target.value })} className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none" />