/** * Daily Work Report - Utilities * 작업보고서 관련 유틸리티 함수들 */ class DailyWorkReportUtils { constructor() { console.log('[Utils] DailyWorkReportUtils 초기화'); } /** * 한국 시간 기준 오늘 날짜 (YYYY-MM-DD) */ getKoreaToday() { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * 날짜를 API 형식(YYYY-MM-DD)으로 변환 */ formatDateForApi(date) { if (!date) return null; let dateObj; if (date instanceof Date) { dateObj = date; } else if (typeof date === 'string') { dateObj = new Date(date); } else { return null; } const year = dateObj.getFullYear(); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const day = String(dateObj.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * 날짜 포맷팅 (표시용) */ formatDate(date) { if (!date) return '-'; let dateObj; if (date instanceof Date) { dateObj = date; } else if (typeof date === 'string') { dateObj = new Date(date); } else { return '-'; } const year = dateObj.getFullYear(); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const day = String(dateObj.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * 시간 포맷팅 (HH:mm) */ formatTime(time) { if (!time) return '-'; if (typeof time === 'string' && time.includes(':')) { return time.substring(0, 5); } return time; } /** * 상태 라벨 반환 */ getStatusLabel(status) { const labels = { 'pending': '접수', 'in_progress': '처리중', 'resolved': '해결', 'completed': '완료', 'closed': '종료' }; return labels[status] || status || '-'; } /** * 숫자 포맷팅 (천 단위 콤마) */ formatNumber(num) { if (num === null || num === undefined) return '0'; return num.toLocaleString('ko-KR'); } /** * 소수점 자리수 포맷팅 */ formatDecimal(num, decimals = 1) { if (num === null || num === undefined) return '0'; return Number(num).toFixed(decimals); } /** * 요일 반환 */ getDayOfWeek(date) { const days = ['일', '월', '화', '수', '목', '금', '토']; const dateObj = date instanceof Date ? date : new Date(date); return days[dateObj.getDay()]; } /** * 오늘인지 확인 */ isToday(date) { const today = this.getKoreaToday(); const targetDate = this.formatDateForApi(date); return today === targetDate; } /** * 두 날짜 사이 일수 계산 */ daysBetween(date1, date2) { const d1 = new Date(date1); const d2 = new Date(date2); const diffTime = Math.abs(d2 - d1); return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); } /** * 디바운스 함수 */ debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * 쓰로틀 함수 */ throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } /** * HTML 이스케이프 */ escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * 객체 깊은 복사 */ deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } /** * 빈 값 확인 */ isEmpty(value) { if (value === null || value === undefined) return true; if (typeof value === 'string') return value.trim() === ''; if (Array.isArray(value)) return value.length === 0; if (typeof value === 'object') return Object.keys(value).length === 0; return false; } /** * 숫자 유효성 검사 */ isValidNumber(value) { return !isNaN(value) && isFinite(value); } /** * 시간 유효성 검사 (0-24) */ isValidHours(hours) { const num = parseFloat(hours); return this.isValidNumber(num) && num >= 0 && num <= 24; } /** * 쿼리 스트링 파싱 */ parseQueryString(queryString) { const params = new URLSearchParams(queryString); const result = {}; for (const [key, value] of params) { result[key] = value; } return result; } /** * 쿼리 스트링 생성 */ buildQueryString(params) { return new URLSearchParams(params).toString(); } /** * 로컬 스토리지 안전하게 가져오기 */ getLocalStorage(key, defaultValue = null) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : defaultValue; } catch (error) { console.error('[Utils] localStorage 읽기 오류:', error); return defaultValue; } } /** * 로컬 스토리지 안전하게 저장하기 */ setLocalStorage(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch (error) { console.error('[Utils] localStorage 저장 오류:', error); return false; } } /** * 배열 그룹화 */ groupBy(array, key) { return array.reduce((result, item) => { const groupKey = typeof key === 'function' ? key(item) : item[key]; if (!result[groupKey]) { result[groupKey] = []; } result[groupKey].push(item); return result; }, {}); } /** * 배열 정렬 (다중 키) */ sortBy(array, ...keys) { return [...array].sort((a, b) => { for (const key of keys) { const direction = key.startsWith('-') ? -1 : 1; const actualKey = key.replace(/^-/, ''); const aVal = a[actualKey]; const bVal = b[actualKey]; if (aVal < bVal) return -1 * direction; if (aVal > bVal) return 1 * direction; } return 0; }); } /** * UUID 생성 */ generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } } // 전역 인스턴스 생성 window.DailyWorkReportUtils = new DailyWorkReportUtils(); // 하위 호환성: 기존 함수들 window.getKoreaToday = () => window.DailyWorkReportUtils.getKoreaToday(); window.formatDateForApi = (date) => window.DailyWorkReportUtils.formatDateForApi(date); window.formatDate = (date) => window.DailyWorkReportUtils.formatDate(date); window.getStatusLabel = (status) => window.DailyWorkReportUtils.getStatusLabel(status); // 메시지 표시 함수들 window.showMessage = function(message, type = 'info') { const container = document.getElementById('message-container'); if (!container) { console.log(`[Message] ${type}: ${message}`); return; } container.innerHTML = `
${message}
`; if (type === 'success') { setTimeout(() => window.hideMessage(), 5000); } }; window.hideMessage = function() { const container = document.getElementById('message-container'); if (container) { container.innerHTML = ''; } }; // 저장 결과 모달 window.showSaveResultModal = function(type, title, message, details = null) { const modal = document.getElementById('saveResultModal'); const titleElement = document.getElementById('resultModalTitle'); const contentElement = document.getElementById('resultModalContent'); if (!modal || !contentElement) { alert(`${title}\n\n${message}`); return; } const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; let content = `
${icons[type] || icons.info}

${title}

${message}

`; if (details) { if (Array.isArray(details) && details.length > 0) { content += `

상세 정보:

`; } else if (typeof details === 'string') { content += `

${details}

`; } } if (titleElement) titleElement.textContent = '저장 결과'; contentElement.innerHTML = content; modal.style.display = 'flex'; // ESC 키로 닫기 const escHandler = (e) => { if (e.key === 'Escape') { window.closeSaveResultModal(); document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); // 배경 클릭으로 닫기 modal.onclick = (e) => { if (e.target === modal) { window.closeSaveResultModal(); } }; }; window.closeSaveResultModal = function() { const modal = document.getElementById('saveResultModal'); if (modal) { modal.style.display = 'none'; } }; // 단계 이동 함수 window.goToStep = function(stepNumber) { const state = window.DailyWorkReportState; for (let i = 1; i <= 3; i++) { const step = document.getElementById(`step${i}`); if (step) { step.classList.remove('active', 'completed'); if (i < stepNumber) { step.classList.add('completed'); const stepNum = step.querySelector('.step-number'); if (stepNum) stepNum.classList.add('completed'); } else if (i === stepNumber) { step.classList.add('active'); } } } window.updateProgressSteps(stepNumber); state.currentStep = stepNumber; }; window.updateProgressSteps = function(currentStepNumber) { for (let i = 1; i <= 3; i++) { const progressStep = document.getElementById(`progressStep${i}`); if (progressStep) { progressStep.classList.remove('active', 'completed'); if (i < currentStepNumber) { progressStep.classList.add('completed'); } else if (i === currentStepNumber) { progressStep.classList.add('active'); } } } }; // 토스트 메시지 (간단 버전) window.showToast = function(message, type = 'info', duration = 3000) { console.log(`[Toast] ${type}: ${message}`); // 기존 토스트 제거 const existingToast = document.querySelector('.toast-message'); if (existingToast) { existingToast.remove(); } // 새 토스트 생성 const toast = document.createElement('div'); toast.className = `toast-message toast-${type}`; toast.textContent = message; toast.style.cssText = ` position: fixed; bottom: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; color: white; font-size: 14px; z-index: 10000; animation: slideIn 0.3s ease; background-color: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : type === 'warning' ? '#f59e0b' : '#3b82f6'}; `; document.body.appendChild(toast); setTimeout(() => { toast.style.animation = 'slideOut 0.3s ease'; setTimeout(() => toast.remove(), 300); }, duration); }; // 확인 다이얼로그 window.showConfirmDialog = function(message, onConfirm, onCancel) { if (confirm(message)) { onConfirm?.(); } else { onCancel?.(); } };