// DashboardApp.jsx - React Dashboard Component for GroupWare const { useState, useEffect, useRef } = React; function DashboardApp() { // 상태 관리 const [dashboardData, setDashboardData] = useState({ presentCount: 0, leaveCount: 0, leaveRequestCount: 0, purchaseCountNR: 0, purchaseCountCR: 0 }); const [isLoading, setIsLoading] = useState(true); const [lastUpdated, setLastUpdated] = useState(''); const [modals, setModals] = useState({ presentUsers: false, holidayUsers: false, holidayRequest: false, purchaseNR: false, purchaseCR: false }); const [modalData, setModalData] = useState({ presentUsers: [], holidayUsers: [], holidayRequests: [], purchaseNR: [], purchaseCR: [] }); const [todoList, setTodoList] = useState([]); // 모달 제어 함수 const showModal = (modalName) => { setModals(prev => ({ ...prev, [modalName]: true })); loadModalData(modalName); }; const hideModal = (modalName) => { setModals(prev => ({ ...prev, [modalName]: false })); }; // Dashboard 데이터 로드 const loadDashboardData = async () => { setIsLoading(true); try { // 실제 DashBoardController API 호출 const [ currentUserResponse, leaveCountResponse, holyRequestResponse, purchaseWaitResponse ] = await Promise.all([ fetch('http://127.0.0.1:7979/DashBoard/GetCurrentUserCount'), fetch('http://127.0.0.1:7979/DashBoard/TodayCountH'), fetch('http://127.0.0.1:7979/DashBoard/GetHolydayRequestCount'), fetch('http://127.0.0.1:7979/DashBoard/GetPurchaseWaitCount') ]); // 현재 근무자 수 (JSON 응답) const currentUserData = await currentUserResponse.json(); const presentCount = currentUserData.Count || 0; // 휴가자 수 (텍스트 응답) const leaveCountText = await leaveCountResponse.text(); const leaveCount = parseInt(leaveCountText.replace(/"/g, ''), 10) || 0; // 휴가 요청 수 (JSON 응답) const holyRequestData = await holyRequestResponse.json(); const leaveRequestCount = holyRequestData.HOLY || 0; // 구매 대기 수 (JSON 응답) const purchaseWaitData = await purchaseWaitResponse.json(); const purchaseCountNR = purchaseWaitData.NR || 0; const purchaseCountCR = purchaseWaitData.CR || 0; setDashboardData({ presentCount, leaveCount, leaveRequestCount, purchaseCountNR, purchaseCountCR }); setLastUpdated(new Date().toLocaleString('ko-KR')); } catch (error) { console.error('대시보드 데이터 로드 실패:', error); } finally { setIsLoading(false); } }; // 모달 데이터 로드 const loadModalData = async (modalName) => { try { let endpoint = ''; switch (modalName) { case 'presentUsers': endpoint = 'http://127.0.0.1:7979/DashBoard/GetPresentUserList'; break; case 'holidayUsers': endpoint = 'http://127.0.0.1:7979/DashBoard/GetholyUser'; break; case 'holidayRequest': endpoint = 'http://127.0.0.1:7979/DashBoard/GetholyRequestUser'; break; case 'purchaseNR': endpoint = 'http://127.0.0.1:7979/DashBoard/GetPurchaseNRList'; break; case 'purchaseCR': endpoint = 'http://127.0.0.1:7979/DashBoard/GetPurchaseCRList'; break; default: return; } const response = await fetch(endpoint); const data = await response.json(); setModalData(prev => ({ ...prev, [modalName]: Array.isArray(data) ? data : [] })); } catch (error) { console.error(`모달 데이터 로드 실패 (${modalName}):`, error); setModalData(prev => ({ ...prev, [modalName]: [] })); } }; // Todo 목록 로드 const loadTodoList = async () => { try { const response = await fetch('http://127.0.0.1:7979/Todo/GetUrgentTodos'); const data = await response.json(); if (data.Success && data.Data) { setTodoList(data.Data); } else { setTodoList([]); } } catch (error) { console.error('Todo 목록 로드 실패:', error); setTodoList([]); } }; // 자동 새로고침 (30초마다) useEffect(() => { loadDashboardData(); loadModalData('holidayUsers'); // 휴가자 목록 자동 로드 loadTodoList(); // Todo 목록 자동 로드 const interval = setInterval(() => { loadDashboardData(); loadModalData('holidayUsers'); // 30초마다 휴가자 목록도 새로고침 loadTodoList(); // 30초마다 Todo 목록도 새로고침 }, 30000); // 30초 return () => clearInterval(interval); }, []); // 통계 카드 컴포넌트 const StatCard = ({ title, count, icon, color, onClick, isClickable = false }) => { return (

{title}

{isLoading ? (

) : count}

{icon}
); }; // 테이블 모달 컴포넌트 const TableModal = ({ isOpen, onClose, title, headers, data, renderRow, maxWidth = 'max-w-4xl' }) => { if (!isOpen) return null; return (
{/* 모달 헤더 */}

{title}

{/* 모달 내용 */}
{headers.map((header, index) => ( ))} {data.length > 0 ? ( data.map((item, index) => renderRow(item, index)) ) : ( )}
{header}
데이터가 없습니다.
{/* 모달 푸터 */}

{data.length}

); }; return (
{/* 헤더 */}

근태현황 대시보드

실시간 근태 및 업무 현황을 확인하세요

{lastUpdated && (

마지막 업데이트: {lastUpdated}

)}
{/* 새로고침 버튼 */}
{/* 통계 카드 */}
showModal('presentUsers')} color="bg-success-500" icon={ } /> showModal('holidayUsers')} color="bg-warning-500" icon={ } /> showModal('holidayRequest')} color="bg-primary-500" icon={ } /> showModal('purchaseNR')} color="bg-danger-500" icon={ } /> showModal('purchaseCR')} color="bg-purple-500" icon={ } />
{/* 2칸 레이아웃: 좌측 휴가현황, 우측 할일 */}
{/* 좌측: 휴가/기타 현황 */}

휴가/기타 현황

{modalData.holidayUsers.map((user, index) => { // 형태에 따른 색상 결정 const typeColorClass = (user.type === '휴가') ? 'bg-green-500/20 text-green-300' : 'bg-warning-500/20 text-warning-300'; // 종류에 따른 색상 결정 let cateColorClass = 'bg-warning-500/20 text-warning-300'; // 기본값 if (user.cate === '휴가') { cateColorClass = 'bg-warning-500/20 text-warning-300'; // 노란색 계열 } else if (user.cate === '파견') { cateColorClass = 'bg-purple-500/20 text-purple-300'; // 보라색 계열 } else { cateColorClass = 'bg-warning-500/20 text-warning-300'; // 기타는 주황색 계열 } // 기간 표시 형식 개선 let periodText = ''; if (user.sdate && user.edate) { if (user.sdate === user.edate) { periodText = user.sdate; } else { periodText = `${user.sdate}~${user.edate}`; } } else { periodText = '-'; } return ( ); })} {modalData.holidayUsers.length === 0 && ( )}
이름 형태 종류 기간 사유
{user.name || '이름 없음'} {user.type || 'N/A'} {user.cate || '종류 없음'} {periodText} {user.title || '사유 없음'}
현재 휴가자가 없습니다
{/* 우측: 할일 */}

할일

{todoList.length > 0 ? ( todoList.map((todo, index) => { const flagIcon = todo.flag ? '📌 ' : ''; // 상태별 클래스 const getTodoStatusClass = (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 getTodoStatusText = (status) => { switch(status) { case '0': return '대기'; case '1': return '진행'; case '2': return '취소'; case '3': return '보류'; case '5': return '완료'; default: return '대기'; } }; const getTodoSeqnoClass = (seqno) => { switch(seqno) { case '0': return 'bg-gray-500/20 text-gray-300'; case '1': return 'bg-success-500/20 text-success-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 getTodoSeqnoText = (seqno) => { switch(seqno) { case '0': return '낮음'; case '1': return '보통'; case '2': return '높음'; case '3': return '긴급'; default: return '보통'; } }; const statusClass = getTodoStatusClass(todo.status); const statusText = getTodoStatusText(todo.status); const seqnoClass = getTodoSeqnoClass(todo.seqno); const seqnoText = getTodoSeqnoText(todo.seqno); const expireText = todo.expire ? new Date(todo.expire).toLocaleDateString('ko-KR') : ''; const isExpired = todo.expire && new Date(todo.expire) < new Date(); const expireClass = isExpired ? 'text-danger-400' : 'text-white/60'; // 만료일이 지난 경우 배경을 적색계통으로 강조 const expiredBgClass = isExpired ? 'bg-danger-600/30 border-danger-400/40 hover:bg-danger-600/40' : 'bg-white/10 hover:bg-white/15 border-white/20'; return (
alert('Todo 상세보기 기능은 준비 중입니다.')}>
{statusText} {seqnoText}
{expireText && ( {expireText} )}

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

{todo.description && (

{todo.description}

)} {todo.request && (

요청자: {todo.request}

)}
); }) ) : (
급한 할일이 없습니다
)}
{/* 출근 대상자 모달 */} hideModal('presentUsers')} title="금일 출근 대상자 목록" headers={['사번', '이름', '공정', '직급', '상태', '이메일']} data={modalData.presentUsers} renderRow={(user, index) => ( {user.id || 'N/A'} {user.name || '이름 없음'} {user.gname || 'N/A'} {user.level || 'N/A'} 출근 {user.email || 'N/A'} )} /> {/* 휴가 요청 모달 */} hideModal('holidayRequest')} title="휴가 신청 목록" maxWidth="max-w-6xl" headers={['사번', '이름', '항목', '일자', '요청일', '요청시간', '비고']} data={modalData.holidayRequests} renderRow={(request, index) => ( {request.uid || 'N/A'} {request.name || '이름 없음'} {request.cate || 'N/A'} {request.sdate && request.edate ? `${request.sdate} ~ ${request.edate}` : 'N/A'} {request.holydays || 'N/A'} {request.holytimes || 'N/A'} {request.HolyReason || request.remark || 'N/A'} )} /> {/* 구매요청 NR 모달 */} hideModal('purchaseNR')} title="구매요청(NR) 목록" maxWidth="max-w-7xl" headers={['요청일', '공정', '품목', '규격', '단위', '수량', '단가', '금액']} data={modalData.purchaseNR} renderRow={(item, index) => ( {item.pdate || 'N/A'} {item.process || 'N/A'} {item.pumname || '품목 없음'} {item.pumscale || 'N/A'} {item.pumunit || 'N/A'} {item.pumqtyreq || 'N/A'} {item.pumprice ? `₩${Number(item.pumprice).toLocaleString()}` : 'N/A'} {item.pumamt ? `₩${Number(item.pumamt).toLocaleString()}` : 'N/A'} )} /> {/* 구매요청 CR 모달 */} hideModal('purchaseCR')} title="구매요청(CR) 목록" maxWidth="max-w-7xl" headers={['요청일', '공정', '품목', '규격', '단위', '수량', '단가', '금액']} data={modalData.purchaseCR} renderRow={(item, index) => ( {item.pdate || 'N/A'} {item.process || 'N/A'} {item.pumname || '품목 없음'} {item.pumscale || 'N/A'} {item.pumunit || 'N/A'} {item.pumqtyreq || 'N/A'} {item.pumprice ? `₩${Number(item.pumprice).toLocaleString()}` : 'N/A'} {item.pumamt ? `₩${Number(item.pumamt).toLocaleString()}` : 'N/A'} )} />
); }