const { useState, useEffect } = React; function Todo() { // 상태 관리 const [todos, setTodos] = useState([]); const [activeTodos, setActiveTodos] = useState([]); const [completedTodos, setCompletedTodos] = useState([]); const [isLoading, setIsLoading] = useState(false); const [currentTab, setCurrentTab] = useState('active'); const [currentEditId, setCurrentEditId] = useState(null); // 모달 상태 const [showAddModal, setShowAddModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); // 폼 상태 const [todoForm, setTodoForm] = useState({ title: '', remark: '', expire: '', seqno: 0, flag: false, request: '', status: '0' }); const [editForm, setEditForm] = useState({ idx: 0, title: '', remark: '', expire: '', seqno: 0, flag: false, request: '', status: '0' }); // 컴포넌트 마운트시 할일 목록 로드 useEffect(() => { loadTodos(); }, []); // 할일 목록을 활성/완료로 분리 useEffect(() => { const active = todos.filter(todo => (todo.status || '0') !== '5'); const completed = todos.filter(todo => (todo.status || '0') === '5'); setActiveTodos(active); setCompletedTodos(completed); }, [todos]); // 할일 목록 로드 const loadTodos = async () => { setIsLoading(true); try { const response = await fetch('/Todo/GetTodos'); const data = await response.json(); if (data.Success) { setTodos(data.Data || []); } else { showNotification(data.Message || '할일 목록을 불러올 수 없습니다.', 'error'); } } catch (error) { console.error('할일 목록 로드 중 오류:', error); showNotification('서버 연결에 실패했습니다.', 'error'); } finally { setIsLoading(false); } }; // 새 할일 추가 const addTodo = async (e) => { e.preventDefault(); if (!todoForm.remark.trim()) { showNotification('할일 내용을 입력해주세요.', 'error'); return; } setIsLoading(true); try { const response = await fetch('/Todo/CreateTodo', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ...todoForm, seqno: parseInt(todoForm.seqno), expire: todoForm.expire || null, request: todoForm.request || null }) }); const data = await response.json(); if (data.Success) { setShowAddModal(false); setTodoForm({ title: '', remark: '', expire: '', seqno: 0, flag: false, request: '', status: '0' }); loadTodos(); showNotification(data.Message || '할일이 추가되었습니다.', 'success'); } else { showNotification(data.Message || '할일 추가에 실패했습니다.', 'error'); } } catch (error) { console.error('할일 추가 중 오류:', error); showNotification('서버 연결에 실패했습니다.', 'error'); } finally { setIsLoading(false); } }; // 할일 수정 const updateTodo = async () => { if (!editForm.remark.trim()) { showNotification('할일 내용을 입력해주세요.', 'error'); return; } setIsLoading(true); try { const response = await fetch('/Todo/UpdateTodo', { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ...editForm, seqno: parseInt(editForm.seqno), expire: editForm.expire || null, request: editForm.request || null }) }); const data = await response.json(); if (data.Success) { setShowEditModal(false); setCurrentEditId(null); loadTodos(); showNotification(data.Message || '할일이 수정되었습니다.', 'success'); } else { showNotification(data.Message || '할일 수정에 실패했습니다.', 'error'); } } catch (error) { console.error('할일 수정 중 오류:', error); showNotification('서버 연결에 실패했습니다.', 'error'); } finally { setIsLoading(false); } }; // 상태 업데이트 (편집 모달에서 바로 서버에 반영) const updateTodoStatus = async (status) => { if (!currentEditId) return; const formData = { ...editForm, status: status, seqno: parseInt(editForm.seqno), expire: editForm.expire || null, request: editForm.request || null }; if (!formData.remark.trim()) { showNotification('할일 내용을 입력해주세요.', 'error'); return; } setIsLoading(true); try { const response = await fetch('/Todo/UpdateTodo', { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); const data = await response.json(); if (data.Success) { setShowEditModal(false); setCurrentEditId(null); loadTodos(); showNotification(`상태가 '${getStatusText(status)}'(으)로 변경되었습니다.`, 'success'); } else { showNotification(data.Message || '상태 변경에 실패했습니다.', 'error'); } } catch (error) { console.error('상태 변경 중 오류:', error); showNotification('서버 연결에 실패했습니다.', 'error'); } finally { setIsLoading(false); } }; // 할일 편집 모달 열기 const editTodo = async (id) => { setCurrentEditId(id); setIsLoading(true); try { const response = await fetch(`/Todo/GetTodo?id=${id}`); const data = await response.json(); if (data.Success && data.Data) { const todo = data.Data; setEditForm({ idx: todo.idx, title: todo.title || '', remark: todo.remark || '', expire: todo.expire ? new Date(todo.expire).toISOString().split('T')[0] : '', seqno: todo.seqno || 0, flag: todo.flag || false, request: todo.request || '', status: todo.status || '0' }); setShowEditModal(true); } else { showNotification(data.Message || '할일 정보를 불러올 수 없습니다.', 'error'); } } catch (error) { console.error('할일 조회 중 오류:', error); showNotification('서버 연결에 실패했습니다.', 'error'); } finally { setIsLoading(false); } }; // 할일 삭제 const deleteTodo = async (id) => { if (!confirm('정말로 이 할일을 삭제하시겠습니까?')) { return; } setIsLoading(true); try { const response = await fetch(`/Todo/DeleteTodo?id=${id}`, { method: 'DELETE' }); const data = await response.json(); if (data.Success) { loadTodos(); showNotification(data.Message || '할일이 삭제되었습니다.', 'success'); } else { showNotification(data.Message || '할일 삭제에 실패했습니다.', 'error'); } } catch (error) { console.error('할일 삭제 중 오류:', error); showNotification('서버 연결에 실패했습니다.', 'error'); } finally { setIsLoading(false); } }; // 유틸리티 함수들 const getStatusClass = (status) => { switch(status) { case '0': return 'bg-gray-500/20 text-gray-300'; case '1': return 'bg-primary-500/20 text-primary-300'; case '2': return 'bg-danger-500/20 text-danger-300'; case '3': return 'bg-warning-500/20 text-warning-300'; case '5': return 'bg-success-500/20 text-success-300'; default: return 'bg-white/10 text-white/50'; } }; const getStatusText = (status) => { switch(status) { case '0': return '대기'; case '1': return '진행'; case '2': return '취소'; case '3': return '보류'; case '5': return '완료'; default: return '대기'; } }; const getSeqnoClass = (seqno) => { switch(seqno) { case 1: return 'bg-primary-500/20 text-primary-300'; case 2: return 'bg-warning-500/20 text-warning-300'; case 3: return 'bg-danger-500/20 text-danger-300'; default: return 'bg-white/10 text-white/50'; } }; const getSeqnoText = (seqno) => { switch(seqno) { case 1: return '중요'; case 2: return '매우 중요'; case 3: return '긴급'; default: return '보통'; } }; const formatDate = (dateString) => { if (!dateString) return '-'; return new Date(dateString).toLocaleDateString('ko-KR'); }; // 알림 표시 함수 const showNotification = (message, type = 'info') => { const colors = { info: 'bg-blue-500/90 backdrop-blur-sm', success: 'bg-green-500/90 backdrop-blur-sm', warning: 'bg-yellow-500/90 backdrop-blur-sm', error: 'bg-red-500/90 backdrop-blur-sm' }; const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 ${colors[type]} text-white px-4 py-3 rounded-lg z-50 transition-all duration-300 transform translate-x-0 opacity-100 shadow-lg border border-white/20`; notification.innerHTML = `
${message}
`; notification.style.transform = 'translateX(100%)'; notification.style.opacity = '0'; document.body.appendChild(notification); setTimeout(() => { notification.style.transform = 'translateX(0)'; notification.style.opacity = '1'; }, 10); setTimeout(() => { notification.style.transform = 'translateX(100%)'; notification.style.opacity = '0'; setTimeout(() => notification.remove(), 300); }, 3000); }; // 할일 행 렌더링 const renderTodoRow = (todo, includeOkdate = false) => { const statusClass = getStatusClass(todo.status); const statusText = getStatusText(todo.status); const flagClass = todo.flag ? 'bg-warning-500/20 text-warning-300' : 'bg-white/10 text-white/50'; const flagText = todo.flag ? '고정' : '일반'; const seqnoClass = getSeqnoClass(todo.seqno); const seqnoText = getSeqnoText(todo.seqno); const expireText = formatDate(todo.expire); const isExpired = todo.expire && new Date(todo.expire) < new Date(); const expireClass = isExpired ? 'text-danger-400' : 'text-white/80'; const okdateText = formatDate(todo.okdate); const okdateClass = todo.okdate ? 'text-success-400' : 'text-white/80'; return ( editTodo(todo.idx)}> {statusText} {flagText} {todo.title || '제목 없음'} {todo.remark || ''} {todo.request || '-'} {seqnoText} {expireText} {includeOkdate && {okdateText}} e.stopPropagation()}> ); }; return (
{/* 할일 목록 */}

내 할일 목록

{/* 탭 메뉴 */}
{/* 진행중인 할일 테이블 */} {currentTab === 'active' && (
{isLoading ? ( ) : activeTodos.length === 0 ? ( ) : ( activeTodos.map(todo => renderTodoRow(todo, false)) )}
진행상태 플래그 제목 내용 요청자 중요도 만료일 작업
데이터를 불러오는 중...
진행중인 할일이 없습니다
)} {/* 완료된 할일 테이블 */} {currentTab === 'completed' && (
{isLoading ? ( ) : completedTodos.length === 0 ? ( ) : ( completedTodos.map(todo => renderTodoRow(todo, true)) )}
진행상태 플래그 제목 내용 요청자 중요도 만료일 완료일 작업
데이터를 불러오는 중...
완료된 할일이 없습니다
)}
{/* 로딩 인디케이터 */} {isLoading && (
처리 중...
)} {/* 새 할일 추가 모달 */} {showAddModal && (
{/* 모달 헤더 */}

새 할일 추가

{/* 모달 내용 */}
setTodoForm({...todoForm, title: e.target.value})} className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" placeholder="할일 제목을 입력하세요" />
setTodoForm({...todoForm, expire: e.target.value})} className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" />
setTodoForm({...todoForm, request: e.target.value})} className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" placeholder="업무 요청자를 입력하세요" />
{[ {value: '0', label: '대기', class: 'bg-gray-500/20 text-gray-300'}, {value: '1', label: '진행', class: 'bg-primary-500/20 text-primary-300'}, {value: '3', label: '보류', class: 'bg-warning-500/20 text-warning-300'}, {value: '2', label: '취소', class: 'bg-danger-500/20 text-danger-300'}, {value: '5', label: '완료', class: 'bg-success-500/20 text-success-300'} ].map(status => ( ))}
{/* 모달 푸터 */}
)} {/* 수정 모달 */} {showEditModal && (
{/* 모달 헤더 */}

할일 수정

{/* 모달 내용 */}
setEditForm({...editForm, title: e.target.value})} className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" placeholder="할일 제목을 입력하세요" />
setEditForm({...editForm, expire: e.target.value})} className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" />
setEditForm({...editForm, request: e.target.value})} className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" placeholder="업무 요청자를 입력하세요" />
{[ {value: '0', label: '대기', class: 'bg-gray-500/20 text-gray-300'}, {value: '1', label: '진행', class: 'bg-primary-500/20 text-primary-300'}, {value: '3', label: '보류', class: 'bg-warning-500/20 text-warning-300'}, {value: '2', label: '취소', class: 'bg-danger-500/20 text-danger-300'}, {value: '5', label: '완료', class: 'bg-success-500/20 text-success-300'} ].map(status => ( ))}
{/* 모달 푸터 */}
)}
); }