import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { ShoppingCart, FileCheck, AlertTriangle, CheckCircle, Flag, RefreshCw, ClipboardList, Clock, FileText, Share2, Lock, List, Plus, X, Loader2, Edit2, Trash2, } from 'lucide-react'; import { comms } from '@/communication'; import { TodoModel, TodoStatus, TodoPriority, PurchaseItem, NoteItem, JobReportItem } from '@/types'; import { NoteViewModal } from '@/components/note/NoteViewModal'; import { NoteEditModal } from '@/components/note/NoteEditModal'; interface StatCardProps { title: string; value: number | string; icon: React.ReactNode; color: string; onClick?: () => void; } function StatCard({ title, value, icon, color, onClick }: StatCardProps) { return (

{title}

{value}

{icon}
); } export function Dashboard() { const navigate = useNavigate(); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); // 통계 데이터 const [purchaseNR, setPurchaseNR] = useState(0); const [purchaseCR, setPurchaseCR] = useState(0); const [todoCount, setTodoCount] = useState(0); const [todayWorkHrs, setTodayWorkHrs] = useState(0); // 목록 데이터 const [urgentTodos, setUrgentTodos] = useState([]); const [purchaseNRList, setPurchaseNRList] = useState([]); const [purchaseCRList, setPurchaseCRList] = useState([]); const [recentNotes, setRecentNotes] = useState([]); // 모달 상태 const [showNRModal, setShowNRModal] = useState(false); const [showCRModal, setShowCRModal] = useState(false); const [showNoteModal, setShowNoteModal] = useState(false); const [showNoteEditModal, setShowNoteEditModal] = useState(false); const [showNoteAddModal, setShowNoteAddModal] = useState(false); const [selectedNote, setSelectedNote] = useState(null); const [editingNote, setEditingNote] = useState(null); const [processing, setProcessing] = useState(false); // 할일 추가 모달 상태 const [showTodoAddModal, setShowTodoAddModal] = useState(false); const [showTodoEditModal, setShowTodoEditModal] = useState(false); const [editingTodo, setEditingTodo] = useState(null); const [todoFormData, setTodoFormData] = useState({ title: '', remark: '', expire: '', seqno: 0 as TodoPriority, flag: false, request: '', status: '0' as TodoStatus, }); const loadDashboardData = useCallback(async () => { try { // 오늘 날짜 (로컬 시간 기준) const now = new Date(); const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; // 현재 로그인 사용자 ID 가져오기 let currentUserId = ''; try { const loginStatus = await comms.checkLoginStatus(); if (loginStatus.Success && loginStatus.IsLoggedIn && loginStatus.User) { currentUserId = loginStatus.User.Id; } } catch (error) { console.error('로그인 정보 로드 오류:', error); } // 병렬로 데이터 로드 const [ purchaseCount, urgentTodosResponse, allTodosResponse, jobreportResponse, notesResponse, ] = await Promise.all([ comms.getPurchaseWaitCount(), comms.getUrgentTodos(), comms.getTodos(), comms.getJobReportList(todayStr, todayStr, currentUserId, ''), comms.getNoteList('2000-01-01', todayStr, ''), ]); setPurchaseNR(purchaseCount.NR); setPurchaseCR(purchaseCount.CR); if (urgentTodosResponse.Success && urgentTodosResponse.Data) { setUrgentTodos(urgentTodosResponse.Data.slice(0, 5)); } if (allTodosResponse.Success && allTodosResponse.Data) { // 진행, 대기 상태의 할일만 카운트 (보류, 취소 제외) const pendingCount = allTodosResponse.Data.filter((t: TodoModel) => t.status === '0' || t.status === '1').length; setTodoCount(pendingCount); } // 오늘 업무일지 작성시간 계산 if (jobreportResponse.Success && jobreportResponse.Data) { const totalHrs = jobreportResponse.Data.reduce((acc: number, item: JobReportItem) => acc + (item.hrs || 0), 0); setTodayWorkHrs(totalHrs); } else { setTodayWorkHrs(0); } // 최근 메모 목록 (최대 10개) if (notesResponse.Success && notesResponse.Data) { setRecentNotes(notesResponse.Data.slice(0, 10)); } } catch (error) { console.error('대시보드 데이터 로드 오류:', error); } finally { setLoading(false); setRefreshing(false); } }, []); const loadNRList = async () => { try { const list = await comms.getPurchaseNRList(); setPurchaseNRList(list); setShowNRModal(true); } catch (error) { console.error('NR 목록 로드 오류:', error); } }; const loadCRList = async () => { try { const list = await comms.getPurchaseCRList(); setPurchaseCRList(list); setShowCRModal(true); } catch (error) { console.error('CR 목록 로드 오류:', error); } }; useEffect(() => { loadDashboardData(); // 30초마다 자동 새로고침 const interval = setInterval(loadDashboardData, 30000); return () => clearInterval(interval); }, [loadDashboardData]); const handleRefresh = () => { setRefreshing(true); loadDashboardData(); }; const getStatusText = (status: string) => { switch (status) { case '0': return '대기'; case '1': return '진행'; case '2': return '취소'; case '3': return '보류'; case '5': return '완료'; default: return '대기'; } }; const getStatusClass = (status: string) => { 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 getPriorityText = (seqno: number) => { switch (seqno) { case 1: return '중요'; case 2: return '매우 중요'; case 3: return '긴급'; default: return '보통'; } }; const getPriorityClass = (seqno: number) => { 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'; } }; if (loading) { return (
); } const handleNoteClick = async (note: NoteItem) => { try { const response = await comms.getNoteDetail(note.idx); if (response.Success && response.Data) { setSelectedNote(response.Data); setShowNoteModal(true); } } catch (error) { console.error('메모 조회 오류:', error); } }; const handleNoteAdd = () => { setEditingNote(null); setShowNoteAddModal(true); }; const handleNoteDelete = async (note: NoteItem) => { setProcessing(true); try { const response = await comms.deleteNote(note.idx); if (response.Success) { setShowNoteModal(false); loadDashboardData(); } else { alert(response.Message || '삭제에 실패했습니다.'); } } catch (error) { console.error('삭제 오류:', error); alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error))); } finally { setProcessing(false); } }; const handleTodoAdd = () => { setTodoFormData({ title: '', remark: '', expire: '', seqno: 0, flag: false, request: '', status: '0', }); setShowTodoAddModal(true); }; const handleTodoEdit = (todo: TodoModel) => { setEditingTodo(todo); setTodoFormData({ title: todo.title || '', remark: todo.remark, expire: todo.expire || '', seqno: todo.seqno as TodoPriority, flag: todo.flag, request: todo.request || '', status: todo.status as TodoStatus, }); setShowTodoEditModal(true); }; const handleTodoSave = async () => { if (!todoFormData.remark.trim()) { alert('할일 내용을 입력해주세요.'); return; } setProcessing(true); try { const response = await comms.createTodo( todoFormData.title, todoFormData.remark, todoFormData.expire || null, todoFormData.seqno, todoFormData.flag, todoFormData.request || null, todoFormData.status ); if (response.Success) { setShowTodoAddModal(false); loadDashboardData(); } else { alert(response.Message || '할일 추가에 실패했습니다.'); } } catch (error) { console.error('할일 추가 오류:', error); alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error))); } finally { setProcessing(false); } }; const handleTodoUpdate = async () => { if (!editingTodo || !todoFormData.remark.trim()) { alert('할일 내용을 입력해주세요.'); return; } setProcessing(true); try { const response = await comms.updateTodo( editingTodo.idx, todoFormData.title, todoFormData.remark, todoFormData.expire || null, todoFormData.seqno, todoFormData.flag, todoFormData.request || null, todoFormData.status ); if (response.Success) { setShowTodoEditModal(false); setEditingTodo(null); loadDashboardData(); } else { alert(response.Message || '할일 수정에 실패했습니다.'); } } catch (error) { console.error('할일 수정 오류:', error); alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error))); } finally { setProcessing(false); } }; const handleTodoDelete = async () => { if (!editingTodo) return; if (!confirm('정말로 이 할일을 삭제하시겠습니까?')) { return; } setProcessing(true); try { const response = await comms.deleteTodo(editingTodo.idx); if (response.Success) { setShowTodoEditModal(false); setEditingTodo(null); loadDashboardData(); } else { alert(response.Message || '할일 삭제에 실패했습니다.'); } } catch (error) { console.error('할일 삭제 오류:', error); alert('서버 연결에 실패했습니다.'); } finally { setProcessing(false); } }; const handleTodoComplete = async () => { if (!editingTodo) return; setProcessing(true); try { const response = await comms.updateTodo( editingTodo.idx, editingTodo.title, editingTodo.remark, editingTodo.expire, editingTodo.seqno, editingTodo.flag, editingTodo.request, '5' // 완료 상태 ); if (response.Success) { setShowTodoEditModal(false); setEditingTodo(null); loadDashboardData(); } else { alert(response.Message || '할일 완료 처리에 실패했습니다.'); } } catch (error) { console.error('할일 완료 오류:', error); alert('서버 연결에 실패했습니다.'); } finally { setProcessing(false); } }; const handleNoteSave = async (formData: { pdate: string; title: string; uid: string; description: string; share: boolean; guid: string; }) => { if (!formData.pdate) { alert('날짜를 입력해주세요.'); return; } if (!formData.title.trim()) { alert('제목을 입력해주세요.'); return; } setProcessing(true); try { const response = editingNote ? await comms.editNote( editingNote.idx, formData.pdate, formData.title, formData.uid, formData.description, '', formData.share, formData.guid ) : await comms.addNote( formData.pdate, formData.title, formData.uid, formData.description, '', formData.share, formData.guid ); if (response.Success) { setShowNoteEditModal(false); setShowNoteAddModal(false); loadDashboardData(); } else { alert(response.Message || '저장에 실패했습니다.'); } } catch (error) { console.error('저장 오류:', error); alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error))); } finally { setProcessing(false); } }; return (
{/* 메인 컨텐츠 */}
{/* 헤더 */}

오늘의 현황

{/* 통계 카드 */}
} color="text-primary-400" onClick={loadNRList} /> } color="text-success-400" onClick={loadCRList} /> } color="text-warning-400" onClick={() => navigate('/todo')} /> } color="text-cyan-400" onClick={() => navigate('/jobreport')} />
{/* 할일 목록 */}

할일

{urgentTodos.length > 0 ? ( urgentTodos.map((todo) => (
handleTodoEdit(todo)} >
{todo.flag && ( )}

{todo.title || '제목 없음'}

{todo.remark}

{getPriorityText(todo.seqno)} {getStatusText(todo.status)} {todo.expire && ( {new Date(todo.expire).toLocaleDateString('ko-KR')} )}
)) ) : (

할일이 없습니다

)}
{/* NR 모달 */} {showNRModal && ( setShowNRModal(false)}> )} {/* CR 모달 */} {showCRModal && ( setShowCRModal(false)}> )} {/* 메모 보기 모달 */} setShowNoteModal(false)} onEdit={(note) => { setShowNoteModal(false); setEditingNote(note); setShowNoteEditModal(true); }} onDelete={handleNoteDelete} /> {/* 메모 편집 모달 */} setShowNoteEditModal(false)} onSave={handleNoteSave} initialEditMode={true} /> {/* 메모 추가 모달 */} setShowNoteAddModal(false)} onSave={handleNoteSave} initialEditMode={true} /> {/* 할일 추가 모달 */} {showTodoAddModal && (
setShowTodoAddModal(false)}>
e.stopPropagation()} > {/* 헤더 */}

할일 추가

{/* 내용 */}
setTodoFormData(prev => ({ ...prev, 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="할일 제목을 입력하세요" />
setTodoFormData(prev => ({ ...prev, 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" />