import { useState, useEffect, useCallback } from 'react'; import { Plus, Edit2, Edit3, Trash2, Flag, Zap, CheckCircle, X, Loader2, RefreshCw, Calendar, Check, } from 'lucide-react'; import { comms } from '@/communication'; import { TodoModel, TodoStatus, TodoPriority } from '@/types'; import { clsx } from 'clsx'; // 상태/중요도 유틸리티 함수들 const getStatusText = (status: string): string => { switch (status) { case '0': return '대기'; case '1': return '진행'; case '2': return '취소'; case '3': return '보류'; case '5': return '완료'; default: return '대기'; } }; const getStatusClass = (status: string): string => { switch (status) { case '0': return 'bg-white/5 text-white/40 border-white/10'; case '1': return 'bg-primary-500/10 text-primary-400 border-primary-500/20'; case '2': return 'bg-danger-500/10 text-danger-400 border-danger-500/20'; case '3': return 'bg-warning-500/10 text-warning-400 border-warning-500/20'; case '5': return 'bg-success-500/10 text-success-400 border-success-500/20'; default: return 'bg-white/5 text-white/30 border-white/5'; } }; const getPriorityText = (seqno: number): string => { switch (seqno) { case -1: return '낮음'; case 1: return '중요'; case 2: return '매우 중요'; case 3: return '긴급'; default: return '보통'; } }; const getPriorityClass = (seqno: number): string => { switch (seqno) { case -1: return 'text-white/20'; case 1: return 'text-primary-400 font-bold'; case 2: return 'text-warning-400 font-bold'; case 3: return 'text-danger-400 font-bold'; default: return 'text-white/40'; } }; // 폼 데이터 타입 interface TodoFormData { title: string; remark: string; expire: string; seqno: TodoPriority; flag: boolean; request: string; status: TodoStatus; } const initialFormData: TodoFormData = { title: '', remark: '', expire: '', seqno: 0, flag: false, request: '', status: '0', }; export function Todo() { const [todos, setTodos] = useState([]); const [loading, setLoading] = useState(true); const [processing, setProcessing] = useState(false); const [activeTab, setActiveTab] = useState<'active' | 'hold' | 'completed' | 'cancelled'>('active'); // 모달 상태 const [showAddModal, setShowAddModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [editingTodo, setEditingTodo] = useState(null); const [formData, setFormData] = useState(initialFormData); // 할일 목록 로드 const loadTodos = useCallback(async () => { try { const response = await comms.getTodos(); if (response.Success && response.Data) { setTodos(response.Data); } } catch (error) { console.error('할일 목록 로드 오류:', error); alert('할일 목록을 불러오는 중 오류가 발생했습니다.'); } finally { setLoading(false); } }, []); useEffect(() => { loadTodos(); }, [loadTodos]); // 필터링된 할일 목록 const activeTodos = todos.filter(todo => todo.status === '0' || todo.status === '1'); // 대기 + 진행 const holdTodos = todos.filter(todo => todo.status === '3'); // 보류 const completedTodos = todos.filter(todo => todo.status === '5'); // 완료 const cancelledTodos = todos.filter(todo => todo.status === '2'); // 취소 // 새 할일 추가 const handleAdd = async () => { if (!formData.remark.trim()) { alert('할일 내용을 입력해주세요.'); return; } setProcessing(true); try { const response = await comms.createTodo( formData.title, formData.remark, formData.expire || null, formData.seqno, formData.flag, formData.request || null, formData.status ); if (response.Success) { setShowAddModal(false); setFormData(initialFormData); loadTodos(); } else { alert(response.Message || '할일 추가에 실패했습니다.'); } } catch (error) { console.error('할일 추가 오류:', error); alert('서버 연결에 실패했습니다.'); } finally { setProcessing(false); } }; // 할일 수정 모달 열기 const openEditModal = async (todo: TodoModel) => { try { const response = await comms.getTodo(todo.idx); if (response.Success && response.Data) { const data = response.Data; setEditingTodo(data); setFormData({ title: data.title || '', remark: data.remark || '', expire: data.expire ? data.expire.split('T')[0] : '', seqno: data.seqno as TodoPriority, flag: data.flag || false, request: data.request || '', status: data.status as TodoStatus, }); setShowEditModal(true); } } catch (error) { console.error('할일 조회 오류:', error); alert('할일 정보를 불러오는 중 오류가 발생했습니다.'); } }; // 할일 수정 const handleUpdate = async () => { if (!editingTodo || !formData.remark.trim()) { alert('할일 내용을 입력해주세요.'); return; } setProcessing(true); try { const response = await comms.updateTodo( editingTodo.idx, formData.title, formData.remark, formData.expire || null, formData.seqno, formData.flag, formData.request || null, formData.status ); if (response.Success) { setShowEditModal(false); setEditingTodo(null); setFormData(initialFormData); loadTodos(); } else { alert(response.Message || '할일 수정에 실패했습니다.'); } } catch (error) { console.error('할일 수정 오류:', error); alert('서버 연결에 실패했습니다.'); } finally { setProcessing(false); } }; // 할일 삭제 const handleDelete = async (id: number) => { if (!confirm('정말로 이 할일을 삭제하시겠습니까?')) { return; } setProcessing(true); try { const response = await comms.deleteTodo(id); if (response.Success) { loadTodos(); } else { alert(response.Message || '할일 삭제에 실패했습니다.'); } } catch (error) { console.error('할일 삭제 오류:', error); alert('서버 연결에 실패했습니다.'); } finally { setProcessing(false); } }; // 상태 빠른 변경 const handleQuickStatusChange = async (todo: TodoModel, newStatus: TodoStatus) => { setProcessing(true); try { const response = await comms.updateTodo( todo.idx, todo.title, todo.remark, todo.expire, todo.seqno, todo.flag, todo.request, newStatus ); if (response.Success) { loadTodos(); } else { alert(response.Message || '상태 변경에 실패했습니다.'); } } catch (error) { console.error('상태 변경 오류:', error); } finally { setProcessing(false); } }; if (loading) { return (
); } return (
{/* 할일 요약 & 컨트롤 */}

내 할일 목록

{/* 개수 표시 */}
{todos.length} Total
{/* 새로고침 */} {/* 추가 버튼 */}
{/* 탭 메뉴 */}
{[ { id: 'active', label: '진행중', icon: Zap, count: activeTodos.length, color: 'primary' }, { id: 'hold', label: '보류', icon: Loader2, count: holdTodos.length, color: 'warning' }, { id: 'completed', label: '완료', icon: CheckCircle, count: completedTodos.length, color: 'success' }, { id: 'cancelled', label: '취소', icon: X, count: cancelledTodos.length, color: 'danger' }, ].map((tab) => ( ))}
{/* 할일 테이블 */}
{activeTab === 'active' && ( )} {(activeTab === 'active' ? activeTodos : activeTab === 'hold' ? holdTodos : activeTab === 'completed' ? completedTodos : cancelledTodos).map((todo) => ( openEditModal(todo)} onComplete={() => handleQuickStatusChange(todo, '5')} /> ))} {(activeTab === 'active' ? activeTodos : activeTab === 'hold' ? holdTodos : activeTab === 'completed' ? completedTodos : cancelledTodos).length === 0 && ( )}
상태 할일 개요 요청자 우선순위 {activeTab === 'completed' ? '완료일' : '만료일'}
{activeTab === 'active' ? '진행중인 할일이 없습니다' : activeTab === 'hold' ? '보류된 할일이 없습니다' : activeTab === 'completed' ? '완료된 할일이 없습니다' : '취소된 할일이 없습니다'}
{/* 로딩 인디케이터 */} {processing && (
처리 중...
)} {/* 추가 모달 */} {showAddModal && ( setShowAddModal(false)} submitText="추가" processing={processing} /> )} {/* 수정 모달 */} {showEditModal && editingTodo && ( { setShowEditModal(false); setEditingTodo(null); }} submitText="수정" processing={processing} isEdit={true} onComplete={() => { handleQuickStatusChange(editingTodo, '5'); setShowEditModal(false); setEditingTodo(null); }} onDelete={() => { handleDelete(editingTodo.idx); setShowEditModal(false); setEditingTodo(null); }} currentStatus={editingTodo.status} /> )}
); } // 할일 행 컴포넌트 interface TodoRowProps { todo: TodoModel; showOkdate: boolean; showCompleteButton?: boolean; onEdit: () => void; onComplete: () => void; } function TodoRow({ todo, showOkdate, showCompleteButton = true, onEdit, onComplete }: TodoRowProps) { const isExpired = todo.expire && new Date(todo.expire) < new Date(); const handleComplete = (e: React.MouseEvent) => { e.stopPropagation(); if (confirm('이 할일을 완료 처리하시겠습니까?')) { onComplete(); } }; return ( {showCompleteButton && ( )} {getStatusText(todo.status)}
{todo.flag && ( )} {todo.title || '제목 없음'}
{todo.request || '-'} 0 ? "fill-current" : "opacity-20")} /> {getPriorityText(todo.seqno)}
{showOkdate ? (todo.okdate ? new Date(todo.okdate).toLocaleDateString('ko-KR', { year: '2-digit', month: '2-digit', day: '2-digit' }) : '-') : (todo.expire ? new Date(todo.expire).toLocaleDateString('ko-KR', { year: '2-digit', month: '2-digit', day: '2-digit' }) : '-') }
); } // 할일 모달 컴포넌트 interface TodoModalProps { title: string; formData: TodoFormData; setFormData: React.Dispatch>; onSubmit: () => void; onClose: () => void; submitText: string; processing: boolean; isEdit?: boolean; onComplete?: () => void; onDelete?: () => void; currentStatus?: string; } function TodoModal({ title, formData, setFormData, onSubmit, onClose, submitText, processing, isEdit = false, onComplete, onDelete, currentStatus, }: TodoModalProps) { const statusOptions: { value: TodoStatus; label: string }[] = [ { value: '0', label: '대기' }, { value: '1', label: '진행' }, { value: '3', label: '보류' }, { value: '2', label: '취소' }, { value: '5', label: '완료' }, ]; return (
e.stopPropagation()}> {/* 헤더 */}
{isEdit ? : }

{title}

{isEdit && onComplete && currentStatus !== '5' && ( )}
{/* 내 */}
setFormData(prev => ({ ...prev, title: e.target.value }))} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-white/20 focus:outline-none focus:ring-1 focus:ring-primary-500/50 transition-all" placeholder="제목입력..." />
setFormData(prev => ({ ...prev, expire: e.target.value }))} className="w-full bg-white/5 border border-white/10 rounded-xl pl-12 pr-4 py-3 text-sm text-white focus:outline-none focus:ring-1 focus:ring-primary-500/50 transition-all [color-scheme:dark]" />