diff --git a/Project/frontend/src/components/common/DevelopmentNotice.tsx b/Project/frontend/src/components/common/DevelopmentNotice.tsx new file mode 100644 index 0000000..99a08b5 --- /dev/null +++ b/Project/frontend/src/components/common/DevelopmentNotice.tsx @@ -0,0 +1,12 @@ + + +export function DevelopmentNotice() { + return ( +
+ + ⚠️ + 현재 개발중인 페이지입니다. 데이터가 정확하지 않을 수 있습니다. + +
+ ); +} diff --git a/Project/frontend/src/components/holiday/HolidayRequestDialog.tsx b/Project/frontend/src/components/holiday/HolidayRequestDialog.tsx index 5de625c..d4a18a7 100644 --- a/Project/frontend/src/components/holiday/HolidayRequestDialog.tsx +++ b/Project/frontend/src/components/holiday/HolidayRequestDialog.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { X, Save, Calendar, Clock, MapPin, User, FileText, AlertCircle } from 'lucide-react'; -import { comms } from '@/communication'; +import { comms } from '../../communication'; +import { DevelopmentNotice } from '../common/DevelopmentNotice'; import { HolidayRequest, CommonCode } from '@/types'; interface HolidayRequestDialogProps { @@ -35,6 +36,7 @@ export function HolidayRequestDialog({ backup: [] }); const [users, setUsers] = useState>([]); + const [adminComments, setAdminComments] = useState([]); // Code 54 // Form State const [formData, setFormData] = useState({ @@ -59,7 +61,9 @@ export function HolidayRequestDialog({ sendmail: false }); + const [balanceMessage, setBalanceMessage] = useState(''); const [requestType, setRequestType] = useState<'day' | 'time' | 'out'>('day'); // day: 휴가, time: 대체, out: 외출 + const isReadOnly = formData.conf === 1 && userLevel < 5; useEffect(() => { if (isOpen) { @@ -67,9 +71,40 @@ export function HolidayRequestDialog({ if (userLevel >= 5) { loadUsers(); } - + if (initialData) { - setFormData({ ...initialData }); + const confValue = initialData.conf; + const convertedConf = Number(initialData.conf ?? 0); + console.log('Dialog Debug:', { + initialData, + rawConf: confValue, + typeOfConf: typeof confValue, + convertedConf, + finalFormDataConf: convertedConf + }); + setFormData({ + idx: initialData.idx, + gcode: initialData.gcode || '', + uid: initialData.uid || currentUserId || '', + cate: initialData.cate || '', + sdate: initialData.sdate ? initialData.sdate.split('T')[0] : new Date().toISOString().split('T')[0], + edate: initialData.edate ? initialData.edate.split('T')[0] : new Date().toISOString().split('T')[0], + HolyDays: initialData.HolyDays || 0, + HolyTimes: initialData.HolyTimes || 0, + HolyReason: initialData.HolyReason || (initialData as any).holyReason || '', + HolyLocation: initialData.HolyLocation || (initialData as any).holyLocation || '', + HolyBackup: initialData.HolyBackup || (initialData as any).holyBackup || '', + Remark: initialData.Remark || (initialData as any).remark || '', + wuid: initialData.wuid || '', + wdate: initialData.wdate || '', + Response: initialData.Response || (initialData as any).response || '', + conf: convertedConf, + stime: initialData.stime || '09:00', + etime: initialData.etime || '18:00', + sendmail: initialData.sendmail || false, + conf_id: initialData.conf_id || '', + conf_time: initialData.conf_time || '' + }); // Determine request type based on data if (initialData.cate === '외출') { setRequestType('out'); @@ -106,21 +141,39 @@ export function HolidayRequestDialog({ } }, [isOpen, initialData, currentUserId]); + // Handle ESC key + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { + if (isOpen && e.key === 'Escape') { + onClose(); + } + }; + window.addEventListener('keydown', handleEsc); + return () => window.removeEventListener('keydown', handleEsc); + }, [isOpen, onClose]); + const loadCodes = async () => { try { - const [cateRes, reasonRes, locationRes, backupRes] = await Promise.all([ - comms.getCommonList('50'), - comms.getCommonList('51'), - comms.getCommonList('52'), - comms.getCommonList('53') - ]); + // Execute sequentially to avoid WebSocket response race conditions + const cateRes = await comms.getCommonList('50'); + const reasonRes = await comms.getCommonList('51'); + const locationRes = await comms.getCommonList('52'); + const backupRes = await comms.getCommonList('53'); + + console.log('Fetched Common Codes:', { + cate: cateRes, + reason: reasonRes, + location: locationRes, + backup: backupRes + }); + setCodes({ cate: cateRes || [], reason: reasonRes || [], location: locationRes || [], backup: backupRes || [] }); - + // Set default category if new if (!initialData && cateRes && cateRes.length > 0) { setFormData(prev => ({ ...prev, cate: cateRes[0].svalue })); @@ -145,12 +198,104 @@ export function HolidayRequestDialog({ } }; + useEffect(() => { + const fetchBalance = async () => { + // Only for new requests + if (formData.idx !== 0 || !isOpen || !formData.sdate || !formData.uid) return; + + try { + const year = formData.sdate.substring(0, 4); + const response = await comms.getHolydayBalance(year, formData.uid); + + // C# logic replication for message formatting + // [기준:YYYY-MM-DD] => [분류] N일 남음(N%사용), ... + const basedate = new Date(formData.sdate); + basedate.setDate(basedate.getDate() - 1); + const baseDateStr = basedate.toISOString().split('T')[0]; // Format manually if needed, but ISO YYYY-MM-DD ok + + if (!response.Success || !response.Data) { + setBalanceMessage(`[기준:${baseDateStr}] => 등록된 근태자료가 없습니다`); + return; + } + + const items: string[] = []; + response.Data.forEach(item => { + // Days + if (item.TotalGenDays !== 0 || item.TotalUseDays !== 0) { // Actually checks if Total != 0 or Remain != 0 in C# + const remain = item.TotalGenDays - item.TotalUseDays; + // C# checks: if (val[0] != "0" || val[2] != "0") (Total or Remain) + // Here checking Total != 0 is usually sufficient. + if (item.TotalGenDays > 0) { + const perc = (item.TotalUseDays / item.TotalGenDays) * 100; + items.push(`[${item.cate}] ${remain.toFixed(1)}일 남음(${perc.toFixed(1)}%사용)`); + } + } + // Times + if (item.TotalGenHours !== 0) { + const remain = item.TotalGenHours - item.TotalUseHours; + if (item.TotalGenHours > 0) { + const perc = (item.TotalUseHours / item.TotalGenHours) * 100; + items.push(`[${item.cate}] ${remain.toFixed(1)}시간 남음(${perc.toFixed(1)}%사용)`); + } + } + }); + + if (items.length === 0) { + setBalanceMessage(`[기준:${baseDateStr}] => 등록된 근태자료가 없습니다`); + } else { + setBalanceMessage(`[기준:${baseDateStr}] => ${items.join(',')}`); + } + + } catch (error) { + console.error('Failed to fetch balance:', error); + } + }; + + fetchBalance(); + }, [formData.sdate, formData.uid, formData.idx, isOpen]); + + + const handleTypeChange = (type: 'day' | 'time' | 'out') => { + setRequestType(type); + setFormData(prev => { + let newCate = prev.cate; + if (type === 'time') newCate = '대체'; + else if (type === 'out') newCate = '외출'; + // If switching back to 'day', we might want to reset cate if it was fixed to '대체' or '외출', + // or just leave it and let the user change it via Select. + // But typically '대체'/'외출' are not valid options for 'day' (general holiday). + // So let's reset to empty or first option if we have codes. + else if (prev.cate === '대체' || prev.cate === '외출') newCate = codes.cate[0]?.svalue || ''; + + return { ...prev, cate: newCate }; + }); + }; + const handleSave = async () => { // Validation if (!formData.cate && requestType === 'day') { alert('구분을 선택하세요.'); return; } + if (requestType === 'day' && (!formData.HolyDays || formData.HolyDays <= 0)) { + alert('일수를 입력하세요.'); + return; + } + if ((requestType === 'time' || requestType === 'out') && (!formData.HolyTimes || formData.HolyTimes <= 0)) { + alert('시간을 입력하세요.'); + return; + } + if (requestType === 'out') { + if (!formData.stime) { + alert('시작 시간을 입력하세요.'); + return; + } + if (!formData.etime) { + alert('종료 시간을 입력하세요.'); + return; + } + } + if (formData.sdate > formData.edate) { alert('종료일이 시작일보다 빠를 수 없습니다.'); return; @@ -161,8 +306,7 @@ export function HolidayRequestDialog({ if (requestType === 'out') { dataToSave.cate = '외출'; dataToSave.HolyDays = 0; - // Calculate times if needed, or rely on user input? - // WinForms doesn't seem to auto-calc times for 'out', just saves stime/etime. + // Calculate times if needed } else if (requestType === 'time') { dataToSave.cate = '대체'; dataToSave.HolyDays = 0; @@ -171,15 +315,24 @@ export function HolidayRequestDialog({ dataToSave.HolyTimes = 0; dataToSave.stime = ''; dataToSave.etime = ''; - - // Calculate days - const start = new Date(dataToSave.sdate); - const end = new Date(dataToSave.edate); - const diffTime = Math.abs(end.getTime() - start.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; - dataToSave.HolyDays = diffDays; + // dataToSave.HolyDays is already set from formData (manual input) } + // New request specific handling + if (formData.idx === 0) { + // Validate Remark is not empty (as per user request) + if (!dataToSave.Remark || dataToSave.Remark.trim() === '') { + alert('비고를 입력해주세요'); + return; // Needs to focus textarea but we can just return + } + + // Append balance message if not already present (simplified check) + if (balanceMessage && !dataToSave.Remark.includes(balanceMessage)) { + dataToSave.Remark = dataToSave.Remark.trim() + '\r\n' + balanceMessage; + } + } + + setLoading(true); try { const response = await comms.saveHolidayRequest(dataToSave); @@ -199,303 +352,384 @@ export function HolidayRequestDialog({ if (!isOpen) return null; + const title = formData.idx === 0 ? '휴가/외출 신청' : '신청 내역 수정'; + return ( -
-
+
+
{/* Header */} -
-

- {formData.idx === 0 ? '휴가/외출 신청' : '신청 내역 수정'} +
+

+ + {title}

-
+ {/* 개발중 알림 */} +
+ +
+ {/* Body */} -
- {/* Request Type */} -
- - - -
- - {/* User Selection (Admin only) */} - {userLevel >= 5 && ( -
- - -
- )} - - {/* Date & Time */} -
-
- - setFormData({ ...formData, sdate: e.target.value })} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
-
- - setFormData({ ...formData, edate: e.target.value })} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
-
- - {(requestType === 'time' || requestType === 'out') && ( -
-
- +
+ {/* Left Column: Inputs */} +
+ {/* Request Type */} +
+
- )} - - {/* Category & Reason */} -
-
- - {requestType === 'day' ? ( +