"use client"; import React, { useState, useEffect } from "react"; import { Plus, Check, X, Clock, AlertCircle, GripVertical, ChevronDown, Calendar as CalendarIcon } from "lucide-react"; import { DashboardElement } from "@/components/admin/dashboard/types"; import { useDashboard } from "@/contexts/DashboardContext"; 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; } interface TodoWidgetProps { element?: DashboardElement; } export default function TodoWidget({ element }: TodoWidgetProps) { // Context에서 선택된 날짜 가져오기 const { selectedDate } = useDashboard(); 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, selectedDate]); // selectedDate도 의존성에 추가 const fetchTodos = async () => { try { const token = localStorage.getItem("authToken"); const userLang = localStorage.getItem("userLang") || "KR"; // 외부 DB 조회 (dataSource가 설정된 경우) if (element?.dataSource?.query) { // console.log("🔍 TodoWidget - 외부 DB 조회 시작"); // console.log("📝 Query:", element.dataSource.query); // console.log("🔗 ConnectionId:", element.dataSource.externalConnectionId); // console.log("🔗 ConnectionType:", element.dataSource.connectionType); // 현재 DB vs 외부 DB 분기 const apiUrl = element.dataSource.connectionType === "external" && element.dataSource.externalConnectionId ? `http://localhost:9771/api/external-db/query?userLang=${userLang}` : `http://localhost:9771/api/dashboards/execute-query?userLang=${userLang}`; const requestBody = element.dataSource.connectionType === "external" && element.dataSource.externalConnectionId ? { connectionId: parseInt(element.dataSource.externalConnectionId), query: element.dataSource.query, } : { query: element.dataSource.query, }; // console.log("🌐 API URL:", apiUrl); // console.log("📦 Request Body:", requestBody); const response = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(requestBody), }); // console.log("📡 Response status:", response.status); if (response.ok) { const result = await response.json(); // console.log("✅ API 응답:", result); // console.log("📦 result.data:", result.data); // console.log("📦 result.data.rows:", result.data?.rows); // API 응답 형식에 따라 데이터 추출 const rows = result.data?.rows || result.data || []; // console.log("📊 추출된 rows:", rows); const externalTodos = mapExternalDataToTodos(rows); // console.log("📋 변환된 Todos:", externalTodos); // console.log("📋 변환된 Todos 개수:", externalTodos.length); setTodos(externalTodos); setStats(calculateStatsFromTodos(externalTodos)); // console.log("✅ setTodos, setStats 호출 완료!"); } else { const errorText = await response.text(); // console.error("❌ API 오류:", errorText); } } // 내장 API 조회 (기본) else { 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); } }; // 외부 DB 데이터를 TodoItem 형식으로 변환 const mapExternalDataToTodos = (data: any[]): TodoItem[] => { return data.map((row, index) => ({ id: row.id || `todo-${index}`, title: row.title || row.task || row.name || "제목 없음", description: row.description || row.desc || row.content, priority: row.priority || "normal", status: row.status || "pending", assignedTo: row.assigned_to || row.assignedTo || row.user, dueDate: row.due_date || row.dueDate || row.deadline, createdAt: row.created_at || row.createdAt || new Date().toISOString(), updatedAt: row.updated_at || row.updatedAt || new Date().toISOString(), completedAt: row.completed_at || row.completedAt, isUrgent: row.is_urgent || row.isUrgent || row.urgent || false, order: row.display_order || row.order || index, })); }; // Todo 배열로부터 통계 계산 const calculateStatsFromTodos = (todoList: TodoItem[]): TodoStats => { return { total: todoList.length, pending: todoList.filter((t) => t.status === "pending").length, inProgress: todoList.filter((t) => t.status === "in_progress").length, completed: todoList.filter((t) => t.status === "completed").length, urgent: todoList.filter((t) => t.isUrgent).length, overdue: todoList.filter((t) => { if (!t.dueDate) return false; return new Date(t.dueDate) < new Date() && t.status !== "completed"; }).length, }; }; // 외부 DB 조회 여부 확인 const isExternalData = !!element?.dataSource?.query; const handleAddTodo = async () => { if (!newTodo.title.trim()) return; if (isExternalData) { alert("외부 데이터베이스 조회 모드에서는 추가할 수 없습니다."); 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 "⚠️ 오늘 마감"; }; // 선택된 날짜로 필터링 const filteredTodos = selectedDate ? todos.filter((todo) => { if (!todo.dueDate) return false; const todoDate = new Date(todo.dueDate); return ( todoDate.getFullYear() === selectedDate.getFullYear() && todoDate.getMonth() === selectedDate.getMonth() && todoDate.getDate() === selectedDate.getDate() ); }) : todos; const formatSelectedDate = () => { if (!selectedDate) return null; const year = selectedDate.getFullYear(); const month = selectedDate.getMonth() + 1; const day = selectedDate.getDate(); return `${year}년 ${month}월 ${day}일`; }; if (loading) { return (
로딩 중...
); } return (
{/* 제목 - 항상 표시 */}

{element?.customTitle || "To-Do / 긴급 지시"}

{selectedDate && (
{formatSelectedDate()} 할일
)}
{/* 헤더 (추가 버튼, 통계, 필터) - showHeader가 false일 때만 숨김 */} {element?.showHeader !== false && (
{/* 통계 */} {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" />