import { useState, useEffect, useCallback } from 'react'; import { FileText, Plus, Trash2, X, Loader2, ChevronDown, Search } from 'lucide-react'; import { createPortal } from 'react-dom'; import { JobReportItem, CommonCode } from '@/types'; import { JobTypeSelectModal } from './JobTypeSelectModal'; import { ProjectSearchDialog } from './ProjectSearchDialog'; import { comms } from '@/communication'; export interface JobreportFormData { pdate: string; projectName: string; pidx: number | null; // 프로젝트 인덱스 (-1이면 프로젝트 연결 없음) requestpart: string; package: string; type: string; process: string; status: string; description: string; hrs: number; ot: number; otStart: string; // 초과근무 시작시간 (HH:mm 형식) otEnd: string; // 초과근무 종료시간 (HH:mm 형식) jobgrp: string; tag: string; } // 날짜 포맷 헬퍼 함수 (로컬 시간 기준) const formatDateLocal = (date: Date) => { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; }; export const initialFormData: JobreportFormData = { pdate: formatDateLocal(new Date()), projectName: '', pidx: null, requestpart: '', package: '', type: '', process: '', status: '진행 완료', description: '', hrs: 8, ot: 0, otStart: '18:00', otEnd: '20:00', jobgrp: '', tag: '', }; interface JobreportEditModalProps { isOpen: boolean; editingItem: JobReportItem | null; formData: JobreportFormData; processing: boolean; onClose: () => void; onFormChange: (data: JobreportFormData) => void; onSave: () => void; onDelete: (idx: number) => void; } export function JobreportEditModal({ isOpen, editingItem, formData, processing, onClose, onFormChange, onSave, onDelete, }: JobreportEditModalProps) { const [showJobTypeModal, setShowJobTypeModal] = useState(false); const [showProjectSearch, setShowProjectSearch] = useState(false); const [requestPartList, setRequestPartList] = useState([]); const [packageList, setPackageList] = useState([]); const [processList, setProcessList] = useState([]); const [statusList, setStatusList] = useState([]); const [loadingCodes, setLoadingCodes] = useState(false); // 프로젝트 변경 추적 (이전 기록 불러오기 여부 판단) const [previousProjectIdx, setPreviousProjectIdx] = useState(null); // ESC 키로 닫기 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && isOpen && !showJobTypeModal && !showProjectSearch) { onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose, showJobTypeModal, showProjectSearch]); // 공용코드 로드 (WebSocket에서 동일 응답타입 충돌 방지를 위해 순차 로드) const loadCommonCodes = useCallback(async () => { setLoadingCodes(true); try { // WebSocket 모드에서는 같은 응답타입을 사용하므로 순차적으로 로드 const requestPart = await comms.getCommonList('13'); // 요청부서 setRequestPartList(requestPart || []); const packages = await comms.getCommonList('14'); // 패키지 setPackageList(packages || []); const processes = await comms.getCommonList('16'); // 공정(프로세스) setProcessList(processes || []); const statuses = await comms.getCommonList('12'); // 상태 setStatusList(statuses || []); } catch (error) { console.error('공용코드 로드 오류:', error); } finally { setLoadingCodes(false); } }, []); useEffect(() => { if (isOpen) { loadCommonCodes(); } }, [isOpen, loadCommonCodes]); // 모달 열릴 때 프로젝트가 설정되어 있으면 자동으로 최종 설정 불러오기 useEffect(() => { const loadLastSettings = async () => { // 신규 등록이고, 프로젝트가 설정되어 있으며, 기본값이 비어있는 경우에만 자동 로드 if (isOpen && !editingItem && formData.pidx && formData.pidx > 0) { // 이미 설정된 값이 있으면 자동 로드하지 않음 (복사 기능 등에서 이미 값이 있을 수 있음) const hasExistingSettings = formData.requestpart || formData.package || formData.type || formData.process; if (hasExistingSettings) { setPreviousProjectIdx(formData.pidx); return; } try { const lastReport = await comms.getLastJobReportByProject(formData.pidx, formData.projectName); if (lastReport.Success && lastReport.Data) { const updatedFormData = { ...formData }; if (lastReport.Data.requestpart) { updatedFormData.requestpart = lastReport.Data.requestpart; } if (lastReport.Data.package) { updatedFormData.package = lastReport.Data.package; } if (lastReport.Data.type) { updatedFormData.type = lastReport.Data.type; } if (lastReport.Data.jobgrp) { updatedFormData.jobgrp = lastReport.Data.jobgrp; } if (lastReport.Data.process) { updatedFormData.process = lastReport.Data.process; } if (lastReport.Data.status) { updatedFormData.status = lastReport.Data.status; } onFormChange(updatedFormData); } setPreviousProjectIdx(formData.pidx); } catch (error) { console.error('최종 설정 불러오기 오류:', error); setPreviousProjectIdx(formData.pidx); } } else if (isOpen && !editingItem) { // 신규 등록인데 프로젝트가 없으면 초기화 setPreviousProjectIdx(null); } }; loadLastSettings(); }, [isOpen, editingItem]); if (!isOpen) return null; const handleFieldChange = ( field: K, value: JobreportFormData[K] ) => { onFormChange({ ...formData, [field]: value }); }; // 업무형태 선택 처리 const handleJobTypeSelect = (process: string, jobgrp: string, type: string) => { // WinForms과 동일하게 N/A 처리: jobgrp만 N/A 처리, process는 빈 값 허용 const normalizedJobgrp = (!jobgrp || jobgrp === '(N/A)') ? 'N/A' : jobgrp; // process가 N/A면 빈 문자열로 (공정 드롭다운에서 선택하도록) const normalizedProcess = (process === 'N/A') ? '' : process; onFormChange({ ...formData, process: normalizedProcess || formData.process, // process가 없으면 기존 값 유지 jobgrp: normalizedJobgrp, type, }); }; // 업무형태 표시 텍스트 (type ← jobgrp 형태, WinForms과 동일) const getJobTypeDisplayText = () => { if (!formData.type) { return '업무형태를 선택하세요'; } // WinForms: fullname = $"{jtype} ← {jgrp}" if (formData.jobgrp && formData.jobgrp !== 'N/A') { return `${formData.type} ← ${formData.jobgrp}`; } return formData.type; }; // 유효성 검사 const handleSaveWithValidation = () => { // 프로젝트명 필수 if (!formData.projectName.trim()) { alert('프로젝트(아이템) 명칭이 없습니다.'); return; } // 업무형태가 '휴가'가 아니면 업무내용 필수 if (formData.type !== '휴가' && !formData.description.trim()) { alert('진행 내용이 없습니다.'); return; } // 근무시간 + 초과시간이 0이면 등록 불가 const totalHours = (formData.hrs || 0) + (formData.ot || 0); if (totalHours === 0) { alert('근무시간/초과시간이 입력되지 않았습니다.'); return; } // 상태 필수 if (!formData.status.trim()) { alert('상태를 선택하세요.'); return; } // 업무형태 필수 if (!formData.type.trim()) { alert('업무형태를 선택하세요.'); return; } // 공정 필수 if (!formData.process.trim()) { alert('업무프로세스를 선택하세요.'); return; } // 유효성 검사 통과, 저장 진행 onSave(); }; return createPortal(
e.stopPropagation()} > {/* 헤더 */}

{editingItem ? '업무일지 수정' : '업무일지 등록'}

{/* 내용 */}
{/* 1행: 날짜, 프로젝트명 */}
handleFieldChange('pdate', e.target.value)} className="w-full bg-white/20 border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400" required />
{ handleFieldChange('projectName', e.target.value); // 프로젝트명을 직접 수정하면 pidx 연결 해제 if (formData.pidx !== null && formData.pidx > 0) { onFormChange({ ...formData, projectName: e.target.value, pidx: -1 }); } }} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); setShowProjectSearch(true); } }} className="flex-1 bg-white/20 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" placeholder="프로젝트명 입력 후 Enter로 검색" required />
{/* 2행: 요청부서, 패키지, 공정 */}
{/* 3행: 업무형태 선택 버튼 */}
{/* 4행: 상태, 근무시간, 초과시간 */}
handleFieldChange('hrs', parseFloat(e.target.value) || 0) } className="w-full bg-white/20 border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400" />
handleFieldChange('ot', parseFloat(e.target.value) || 0) } className="w-full bg-white/20 border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400" />
{/* 5행: 초과근무 시간대 (OT > 0일 때만 표시) */} {formData.ot > 0 && (
handleFieldChange('otStart', e.target.value)} className="w-full bg-white/20 border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400" />
handleFieldChange('otEnd', e.target.value)} className="w-full bg-white/20 border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400" />
)} {/* 업무내용 */}