import { useState, useEffect, useCallback } from 'react'; import { FileText, Search, RefreshCw, Copy, Plus, Calendar, } from 'lucide-react'; import { comms } from '@/communication'; import { JobReportItem, JobReportUser } from '@/types'; import { JobreportEditModal, JobreportFormData, initialFormData } from '@/components/jobreport/JobreportEditModal'; import { JobReportDayDialog } from '@/components/jobreport/JobReportDayDialog'; import { JobreportTypeModal } from '@/components/jobreport/JobreportTypeModal'; export function Jobreport() { const [jobreportList, setJobreportList] = useState([]); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); const [processing, setProcessing] = useState(false); // 검색 조건 const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [selectedUser, setSelectedUser] = useState(''); const [searchKey, setSearchKey] = useState(''); // 모달 상태 const [showModal, setShowModal] = useState(false); const [showDayReportModal, setShowDayReportModal] = useState(false); const [showTypeReportModal, setShowTypeReportModal] = useState(false); const [editingItem, setEditingItem] = useState(null); const [formData, setFormData] = useState(initialFormData); // 페이징 상태 const [currentPage, setCurrentPage] = useState(1); const pageSize = 10; // 권한 상태 const [canViewOT, setCanViewOT] = useState(false); // 오늘 근무시간 상태 const [todayWork, setTodayWork] = useState({ hrs: 0, ot: 0 }); // 날짜 포맷 헬퍼 함수 (로컬 시간 기준) const formatDateLocal = (date: Date) => { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; }; // 오늘 근무시간 로드 const loadTodayWork = useCallback(async (userId: string) => { const todayStr = formatDateLocal(new Date()); try { const response = await comms.getJobReportList(todayStr, todayStr, userId, ''); if (response.Success && response.Data) { // 웹소켓 모드에서 응답 혼선 방지를 위해 오늘 날짜 데이터만 필터링 const todayData = response.Data.filter(item => { const itemDate = item.pdate?.substring(0, 10); return itemDate === todayStr; }); const work = todayData.reduce((acc, item) => ({ hrs: acc.hrs + (item.hrs || 0), ot: acc.ot + (item.ot || 0) }), { hrs: 0, ot: 0 }); setTodayWork(work); } } catch (error) { console.error('오늘 근무시간 로드 오류:', error); } }, []); // 초기화 완료 플래그 const [initialized, setInitialized] = useState(false); // 날짜 및 사용자 정보 초기화 useEffect(() => { const initialize = async () => { const now = new Date(); const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0); const sd = formatDateLocal(startOfMonth); const ed = formatDateLocal(endOfMonth); setStartDate(sd); setEndDate(ed); // 현재 로그인 사용자 정보 로드 let userId = ''; try { const loginStatus = await comms.checkLoginStatus(); if (loginStatus.Success && loginStatus.IsLoggedIn && loginStatus.User) { userId = loginStatus.User.Id; setSelectedUser(userId); } } catch (error) { console.error('로그인 정보 로드 오류:', error); } // 사용자 목록 로드 loadUsers(); // 권한 로드 (본인 조회이므로 canViewOT = true) try { const perm = await comms.getJobReportPermission(userId); if (perm.Success) { setCanViewOT(perm.CanViewOT); } } catch (error) { console.error('권한 정보 로드 오류:', error); } // 초기화 완료 표시 setInitialized(true); }; initialize(); }, []); // 초기화 완료 후 조회 실행 useEffect(() => { if (initialized && startDate && endDate && selectedUser) { handleSearchAndLoadToday(); } }, [initialized, startDate, endDate, selectedUser]); // 검색 + 오늘 근무시간 로드 (순차 실행) const handleSearchAndLoadToday = async () => { await handleSearch(); loadTodayWork(selectedUser); }; // 사용자 목록 로드 const loadUsers = async () => { try { const result = await comms.getJobReportUsers(); setUsers(result || []); } catch (error) { console.error('사용자 목록 로드 오류:', error); } }; // 데이터 로드 const loadData = useCallback(async () => { if (!startDate || !endDate) return; setLoading(true); try { const response = await comms.getJobReportList(startDate, endDate, selectedUser, searchKey); if (response.Success && response.Data) { setJobreportList(response.Data); } else { setJobreportList([]); } } catch (error) { console.error('업무일지 목록 로드 오류:', error); alert('데이터를 불러오는 중 오류가 발생했습니다.'); } finally { setLoading(false); } }, [startDate, endDate, selectedUser, searchKey]); // 검색 const handleSearch = async () => { if (new Date(startDate) > new Date(endDate)) { alert('시작일은 종료일보다 늦을 수 없습니다.'); return; } // 선택된 담당자에 따라 권한 재확인 try { const perm = await comms.getJobReportPermission(selectedUser); if (perm.Success) { setCanViewOT(perm.CanViewOT); } } catch (error) { console.error('권한 정보 로드 오류:', error); } await loadData(); }; // 새 업무일지 추가 모달 const openAddModal = () => { setEditingItem(null); setFormData(initialFormData); setShowModal(true); }; // 복사하여 새 업무일지 생성 모달 const openCopyModal = async (item: JobReportItem, e: React.MouseEvent) => { e.stopPropagation(); // 행 클릭 이벤트 방지 try { const response = await comms.getJobReportDetail(item.idx); if (response.Success && response.Data) { const data = response.Data; setEditingItem(null); // 새로 추가하는 것이므로 null setFormData({ pdate: new Date().toISOString().split('T')[0], // 오늘 날짜 projectName: data.projectName || '', pidx: data.pidx ?? null, // pidx도 복사 requestpart: data.requestpart || '', package: data.package || '', type: data.type || '', process: data.process || '', status: data.status || '진행 완료', description: data.description || '', hrs: 0, // 시간 초기화 ot: 0, // OT 초기화 otStart: data.otStart ? data.otStart.substring(11, 16) : '18:00', otEnd: data.otEnd ? data.otEnd.substring(11, 16) : '20:00', jobgrp: '', tag: '', }); setShowModal(true); } } catch (error) { console.error('업무일지 조회 오류:', error); alert('데이터를 불러오는 중 오류가 발생했습니다.'); } }; // 편집 모달 const openEditModal = async (item: JobReportItem) => { try { const response = await comms.getJobReportDetail(item.idx); if (response.Success && response.Data) { const data = response.Data; setEditingItem(data); setFormData({ pdate: data.pdate ? data.pdate.split('T')[0] : '', projectName: data.projectName || '', pidx: data.pidx ?? null, requestpart: data.requestpart || '', package: data.package || '', type: data.type || '', process: data.process || '', status: data.status || '진행 완료', description: data.description || '', hrs: data.hrs || 0, ot: data.ot || 0, otStart: data.otStart ? data.otStart.substring(11, 16) : '18:00', otEnd: data.otEnd ? data.otEnd.substring(11, 16) : '20:00', jobgrp: data.jobgrp || '', tag: data.tag || '', }); setShowModal(true); } } catch (error) { console.error('업무일지 조회 오류:', error); alert('데이터를 불러오는 중 오류가 발생했습니다.'); } }; // 저장 const handleSave = async () => { if (!formData.pdate) { alert('날짜를 입력해주세요.'); return; } if (!formData.projectName.trim()) { alert('프로젝트명을 입력해주세요.'); return; } setProcessing(true); try { let response; if (editingItem) { const itemIdx = editingItem.idx ?? (editingItem as unknown as Record)['Idx'] as number; if (!itemIdx) { alert('수정할 항목의 ID를 찾을 수 없습니다.'); setProcessing(false); return; } response = await comms.editJobReport( itemIdx, formData.pdate || '', formData.projectName || '', formData.pidx, formData.requestpart || '', formData.package || '', formData.type || '', formData.process || '', formData.status || '진행 완료', formData.description || '', formData.hrs || 0, formData.ot || 0, formData.jobgrp || '', formData.tag || '', formData.otStart || '18:00', formData.otEnd || '20:00' ); } else { response = await comms.addJobReport( formData.pdate || '', formData.projectName || '', formData.pidx, formData.requestpart || '', formData.package || '', formData.type || '', formData.process || '', formData.status || '진행 완료', formData.description || '', formData.hrs || 0, formData.ot || 0, formData.jobgrp || '', formData.tag || '', formData.otStart || '18:00', formData.otEnd || '20:00' ); } if (response.Success) { setShowModal(false); loadData(); loadTodayWork(selectedUser); } else { alert(response.Message || '저장에 실패했습니다.'); } } catch (error) { console.error('저장 오류:', error); alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error))); } finally { setProcessing(false); } }; // 삭제 const handleDelete = async (id: number) => { if (!confirm('정말로 이 업무일지를 삭제하시겠습니까?')) return; setProcessing(true); try { const response = await comms.deleteJobReport(id); if (response.Success) { alert('삭제되었습니다.'); loadData(); loadTodayWork(selectedUser); } else { alert(response.Message || '삭제에 실패했습니다.'); } } catch (error) { console.error('삭제 오류:', error); alert('서버 연결에 실패했습니다.'); } finally { setProcessing(false); } }; // 날짜 포맷 (YY.MM.DD) const formatDate = (dateStr: string | null) => { if (!dateStr) return '-'; try { const date = new Date(dateStr); const yy = String(date.getFullYear()).slice(-2); const mm = String(date.getMonth() + 1).padStart(2, '0'); const dd = String(date.getDate()).padStart(2, '0'); return `${yy}.${mm}.${dd}`; } catch { return dateStr; } }; // 페이징 계산 const totalPages = Math.ceil(jobreportList.length / pageSize); const paginatedList = jobreportList.slice( (currentPage - 1) * pageSize, currentPage * pageSize ); // 검색 시 페이지 초기화 const handleSearchWithReset = () => { setCurrentPage(1); handleSearch(); }; return (
{/* 검색 필터 */}
{/* 좌측: 필터 영역 */}
{/* 필터 입력 영역: 2행 2열 */}
{/* 1행: 시작일, 담당자 */}
setStartDate(e.target.value)} className="w-36 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400" />
{/* 2행: 종료일, 검색어 */}
setEndDate(e.target.value)} className="w-36 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400" />
setSearchKey(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearchWithReset()} placeholder="프로젝트, 내용 등" className="w-44 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400" />
{/* 버튼 영역: 우측 수직 배치 */}
{/* 중앙: 집계 메뉴 */}
{/* 우측: 오늘 근무시간 */}
오늘 근무시간
{todayWork.hrs} 시간
{todayWork.ot > 0 && (
OT: {todayWork.ot}시간
)}
{/* 데이터 테이블 */}

업무일지 목록

{jobreportList.length}건
{canViewOT && } {loading ? ( ) : jobreportList.length === 0 ? ( ) : ( paginatedList.map((item) => ( openEditModal(item)} > {canViewOT && ( )} )) )}
날짜 프로젝트 업무형태 상태 시간OT담당자
데이터를 불러오는 중...
조회된 데이터가 없습니다.
{formatDate(item.pdate)} 0 ? 'text-white' : 'text-white/50'}`} title={item.projectName}> {item.projectName || '-'} {item.type || '-'} {item.status || '-'} {item.hrs || 0}h {item.ot ? {item.ot}h : '-'} {item.name || item.id || '-'}
{/* 페이징 */} {totalPages > 1 && (
총 {jobreportList.length}건 중 {(currentPage - 1) * pageSize + 1}-{Math.min(currentPage * pageSize, jobreportList.length)}건
{currentPage} / {totalPages}
)}
{/* 추가/수정 모달 */} setShowModal(false)} onFormChange={setFormData} onSave={handleSave} onDelete={(idx) => { handleDelete(idx); setShowModal(false); }} /> {/* 일별 집계 모달 */} setShowDayReportModal(false)} initialMonth={startDate.substring(0, 7)} /> {/* 업무형태별 집계 모달 */} setShowTypeReportModal(false)} startDate={startDate} endDate={endDate} userId={selectedUser} />
); }