투두리스트랑 커스텀통계카드
This commit is contained in:
@@ -38,6 +38,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
const { selectedDate } = useDashboard();
|
||||
|
||||
const [todos, setTodos] = useState<TodoItem[]>([]);
|
||||
const [internalTodos, setInternalTodos] = useState<TodoItem[]>([]); // 내장 API 투두
|
||||
const [stats, setStats] = useState<TodoStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState<"all" | "pending" | "in_progress" | "completed">("all");
|
||||
@@ -62,6 +63,21 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
const token = localStorage.getItem("authToken");
|
||||
const userLang = localStorage.getItem("userLang") || "KR";
|
||||
|
||||
// 내장 API 투두 항상 조회 (외부 DB 모드에서도)
|
||||
const filterParam = filter !== "all" ? `?status=${filter}` : "";
|
||||
const internalResponse = await fetch(`http://localhost:9771/api/todos${filterParam}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
let internalData: TodoItem[] = [];
|
||||
if (internalResponse.ok) {
|
||||
const result = await internalResponse.json();
|
||||
internalData = result.data || [];
|
||||
setInternalTodos(internalData);
|
||||
}
|
||||
|
||||
// 외부 DB 조회 (dataSource가 설정된 경우)
|
||||
if (element?.dataSource?.query) {
|
||||
// console.log("🔍 TodoWidget - 외부 DB 조회 시작");
|
||||
@@ -111,8 +127,10 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
// console.log("📋 변환된 Todos:", externalTodos);
|
||||
// console.log("📋 변환된 Todos 개수:", externalTodos.length);
|
||||
|
||||
setTodos(externalTodos);
|
||||
setStats(calculateStatsFromTodos(externalTodos));
|
||||
// 외부 DB 데이터 + 내장 데이터 합치기
|
||||
const mergedTodos = [...externalTodos, ...internalData];
|
||||
setTodos(mergedTodos);
|
||||
setStats(calculateStatsFromTodos(mergedTodos));
|
||||
|
||||
// console.log("✅ setTodos, setStats 호출 완료!");
|
||||
} else {
|
||||
@@ -120,20 +138,10 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
// console.error("❌ API 오류:", errorText);
|
||||
}
|
||||
}
|
||||
// 내장 API 조회 (기본)
|
||||
// 내장 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);
|
||||
}
|
||||
setTodos(internalData);
|
||||
setStats(calculateStatsFromTodos(internalData));
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("To-Do 로딩 오류:", error);
|
||||
@@ -180,10 +188,6 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
|
||||
const handleAddTodo = async () => {
|
||||
if (!newTodo.title.trim()) return;
|
||||
if (isExternalData) {
|
||||
alert("외부 데이터베이스 조회 모드에서는 추가할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem("authToken");
|
||||
@@ -325,28 +329,31 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-blue-50">
|
||||
{/* 제목 - 항상 표시 */}
|
||||
<div className="border-b border-gray-200 bg-white px-4 py-2">
|
||||
<h3 className="text-lg font-bold text-gray-800">{element?.customTitle || "To-Do / 긴급 지시"}</h3>
|
||||
{selectedDate && (
|
||||
<div className="mt-1 flex items-center gap-1 text-xs text-green-600">
|
||||
<CalendarIcon className="h-3 w-3" />
|
||||
<span className="font-semibold">{formatSelectedDate()} 할일</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-gray-800">{element?.customTitle || "To-Do / 긴급 지시"}</h3>
|
||||
{selectedDate && (
|
||||
<div className="mt-1 flex items-center gap-1 text-xs text-green-600">
|
||||
<CalendarIcon className="h-3 w-3" />
|
||||
<span className="font-semibold">{formatSelectedDate()} 할일</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* 추가 버튼 - 항상 표시 */}
|
||||
<button
|
||||
onClick={() => setShowAddForm(!showAddForm)}
|
||||
className="flex items-center gap-1 rounded-lg bg-primary px-3 py-1.5 text-sm text-white transition-colors hover:bg-primary/90"
|
||||
title="할 일 추가"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 헤더 (추가 버튼, 통계, 필터) - showHeader가 false일 때만 숨김 */}
|
||||
{/* 헤더 (통계, 필터) - showHeader가 false일 때만 숨김 */}
|
||||
{element?.showHeader !== false && (
|
||||
<div className="border-b border-gray-200 bg-white px-4 py-3">
|
||||
<div className="mb-3 flex items-center justify-end">
|
||||
<button
|
||||
onClick={() => setShowAddForm(!showAddForm)}
|
||||
className="flex items-center gap-1 rounded-lg bg-primary px-3 py-1.5 text-sm text-white transition-colors hover:bg-primary/90"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 통계 */}
|
||||
{stats && (
|
||||
<div className="grid grid-cols-4 gap-2 text-xs mb-3">
|
||||
@@ -390,19 +397,21 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
|
||||
{/* 추가 폼 */}
|
||||
{showAddForm && (
|
||||
<div className="border-b border-gray-200 bg-white p-4">
|
||||
<div className="max-h-[400px] overflow-y-auto border-b border-gray-200 bg-white p-4">
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="할 일 제목*"
|
||||
value={newTodo.title}
|
||||
onChange={(e) => setNewTodo({ ...newTodo, title: e.target.value })}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
|
||||
/>
|
||||
<textarea
|
||||
placeholder="상세 설명 (선택)"
|
||||
value={newTodo.description}
|
||||
onChange={(e) => setNewTodo({ ...newTodo, description: e.target.value })}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
|
||||
rows={2}
|
||||
/>
|
||||
@@ -454,7 +463,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
|
||||
)}
|
||||
|
||||
{/* To-Do 리스트 */}
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<div className="flex-1 overflow-y-auto p-4 min-h-0">
|
||||
{filteredTodos.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center text-gray-400">
|
||||
<div className="text-center">
|
||||
|
||||
Reference in New Issue
Block a user