/** * daily-status.js — 일별 TBM/작업보고서 입력 현황 대시보드 * Sprint 002 Section B */ // ===== Mock 설정 ===== const MOCK_ENABLED = false; const MOCK_DATA = { success: true, data: { date: '2026-03-30', summary: { total_active_workers: 45, tbm_completed: 38, tbm_missing: 7, report_completed: 35, report_missing: 10, both_completed: 33, both_missing: 5 }, workers: [ { user_id: 15, worker_name: '김철수', job_type: '용접', department_name: '생산1팀', has_tbm: false, has_report: false, tbm_session_id: null, total_report_hours: 0, status: 'both_missing', proxy_history: null }, { user_id: 22, worker_name: '이영희', job_type: '배관', department_name: '생산2팀', has_tbm: true, has_report: false, tbm_session_id: 140, total_report_hours: 0, status: 'tbm_only', proxy_history: null }, { user_id: 30, worker_name: '박민수', job_type: '전기', department_name: '생산1팀', has_tbm: true, has_report: true, tbm_session_id: 141, total_report_hours: 8, status: 'complete', proxy_history: { proxy_by: '관리자', proxy_at: '2026-03-30T14:30:00' } }, { user_id: 35, worker_name: '정대호', job_type: '도장', department_name: '생산2팀', has_tbm: false, has_report: true, tbm_session_id: null, total_report_hours: 8, status: 'report_only', proxy_history: null }, { user_id: 40, worker_name: '최윤서', job_type: '용접', department_name: '생산1팀', has_tbm: true, has_report: true, tbm_session_id: 142, total_report_hours: 9, status: 'complete', proxy_history: null }, { user_id: 41, worker_name: '한지민', job_type: '사상', department_name: '생산2팀', has_tbm: false, has_report: false, tbm_session_id: null, total_report_hours: 0, status: 'both_missing', proxy_history: null }, { user_id: 42, worker_name: '송민호', job_type: '절단', department_name: '생산1팀', has_tbm: true, has_report: true, tbm_session_id: 143, total_report_hours: 8, status: 'complete', proxy_history: null }, ] } }; const MOCK_DETAIL = { success: true, data: { worker: { user_id: 15, worker_name: '김철수', job_type: '용접', department_name: '생산1팀' }, tbm_sessions: [], work_reports: [], proxy_history: [] } }; // ===== State ===== let currentDate = new Date(); let workers = []; let currentFilter = 'all'; let selectedWorkerId = null; const DAYS_KR = ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일']; const ALLOWED_ROLES = ['support_team', 'admin', 'system']; // ===== Init ===== document.addEventListener('DOMContentLoaded', async () => { // URL 파라미터에서 날짜 가져오기 const urlDate = new URLSearchParams(location.search).get('date'); if (urlDate) currentDate = new Date(urlDate + 'T00:00:00'); // 권한 체크 (initAuth 완료 후) setTimeout(() => { const user = window.currentUser; if (user && !ALLOWED_ROLES.includes(user.role)) { document.getElementById('workerList').classList.add('hidden'); document.getElementById('bottomAction').classList.add('hidden'); document.getElementById('noPermission').classList.remove('hidden'); return; } loadStatus(); }, 500); }); // ===== Date Navigation ===== function formatDateStr(d) { return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); } function updateDateDisplay() { const str = formatDateStr(currentDate); document.getElementById('dateText').textContent = str; document.getElementById('dayText').textContent = DAYS_KR[currentDate.getDay()]; // 미래 날짜 비활성 const today = new Date(); today.setHours(0, 0, 0, 0); const nextBtn = document.getElementById('nextDate'); nextBtn.disabled = currentDate >= today; } function changeDate(delta) { currentDate.setDate(currentDate.getDate() + delta); updateDateDisplay(); loadStatus(); } function openDatePicker() { const picker = document.getElementById('datePicker'); picker.value = formatDateStr(currentDate); picker.max = formatDateStr(new Date()); picker.showPicker ? picker.showPicker() : picker.click(); } function onDatePicked(val) { if (!val) return; currentDate = new Date(val + 'T00:00:00'); updateDateDisplay(); loadStatus(); } // ===== Data Loading ===== async function loadStatus() { const listEl = document.getElementById('workerList'); listEl.innerHTML = '
'; document.getElementById('emptyState').classList.add('hidden'); updateDateDisplay(); try { let res; if (MOCK_ENABLED) { res = MOCK_DATA; } else { res = await window.apiCall('/proxy-input/daily-status?date=' + formatDateStr(currentDate)); } if (!res || !res.success) { listEl.innerHTML = '

데이터를 불러올 수 없습니다

'; return; } workers = res.data.workers || []; updateSummary(res.data.summary || {}); updateFilterCounts(); renderWorkerList(); } catch (e) { listEl.innerHTML = '

네트워크 오류. 다시 시도해주세요.

'; } } function updateSummary(s) { document.getElementById('totalCount').textContent = s.total_active_workers || 0; document.getElementById('doneCount').textContent = s.both_completed || 0; document.getElementById('missingCount').textContent = s.both_missing || 0; const total = s.total_active_workers || 1; document.getElementById('donePct').textContent = Math.round((s.both_completed || 0) / total * 100) + '%'; document.getElementById('missingPct').textContent = Math.round((s.both_missing || 0) / total * 100) + '%'; // 하단 버튼 카운트 const missingWorkers = workers.filter(w => w.status !== 'complete').length; document.getElementById('proxyCount').textContent = missingWorkers; document.getElementById('proxyBtn').disabled = missingWorkers === 0; } function updateFilterCounts() { document.getElementById('filterAll').textContent = workers.length; document.getElementById('filterComplete').textContent = workers.filter(w => w.status === 'complete').length; document.getElementById('filterMissing').textContent = workers.filter(w => w.status === 'both_missing').length; document.getElementById('filterPartial').textContent = workers.filter(w => w.status === 'tbm_only' || w.status === 'report_only').length; } // ===== Filter ===== function setFilter(f) { currentFilter = f; document.querySelectorAll('.ds-tab').forEach(tab => { tab.classList.toggle('active', tab.dataset.filter === f); }); renderWorkerList(); } // ===== Render ===== function renderWorkerList() { const listEl = document.getElementById('workerList'); const emptyEl = document.getElementById('emptyState'); let filtered = workers; if (currentFilter === 'complete') filtered = workers.filter(w => w.status === 'complete'); else if (currentFilter === 'both_missing') filtered = workers.filter(w => w.status === 'both_missing'); else if (currentFilter === 'partial') filtered = workers.filter(w => w.status === 'tbm_only' || w.status === 'report_only'); if (filtered.length === 0) { listEl.innerHTML = ''; emptyEl.classList.remove('hidden'); return; } emptyEl.classList.add('hidden'); listEl.innerHTML = filtered.map(w => { const tbmBadge = w.has_tbm ? 'TBM ✓' : 'TBM ✗'; const reportBadge = w.has_report ? `보고서 ✓${w.total_report_hours ? ' ' + w.total_report_hours + 'h' : ''}` : '보고서 ✗'; const isProxy = w.tbm_sessions?.some(t => t.is_proxy_input) || false; const proxyBadge = isProxy ? '대리입력' : ''; return `
${escHtml(w.worker_name)}
${escHtml(w.job_type)} · ${escHtml(w.department_name)}
${tbmBadge}${reportBadge}${proxyBadge}
`; }).join(''); } function escHtml(s) { return (s || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } // ===== Bottom Sheet ===== function openSheet(userId) { selectedWorkerId = userId; const w = workers.find(x => x.user_id === userId); if (!w) return; document.getElementById('sheetWorkerName').textContent = w.worker_name; document.getElementById('sheetWorkerInfo').textContent = `${w.job_type} · ${w.department_name}`; document.getElementById('sheetBody').innerHTML = '
로딩 중...
'; document.getElementById('sheetOverlay').classList.remove('hidden'); document.getElementById('detailSheet').classList.remove('hidden'); setTimeout(() => document.getElementById('detailSheet').classList.add('open'), 10); // 상세 데이터 로드 loadDetail(userId, w); } async function loadDetail(userId, workerBasic) { const bodyEl = document.getElementById('sheetBody'); try { let res; if (MOCK_ENABLED) { res = JSON.parse(JSON.stringify(MOCK_DETAIL)); res.data.worker = workerBasic; // mock: complete 상태면 TBM/보고서 데이터 채우기 if (workerBasic.has_tbm) { res.data.tbm_sessions = [{ session_id: workerBasic.tbm_session_id, session_date: formatDateStr(currentDate), status: 'completed', leader_name: '반장' }]; } if (workerBasic.has_report) { res.data.work_reports = [{ report_date: formatDateStr(currentDate), project_name: '프로젝트A', work_type_name: workerBasic.job_type, work_hours: workerBasic.total_report_hours }]; } if (workerBasic.proxy_history) { res.data.proxy_history = [workerBasic.proxy_history]; } } else { res = await window.apiCall('/proxy-input/daily-status/detail?date=' + formatDateStr(currentDate) + '&user_id=' + userId); } if (!res || !res.success) { bodyEl.innerHTML = '
상세 정보를 불러올 수 없습니다
'; return; } const d = res.data; let html = ''; // TBM 섹션 html += '
TBM
'; if (d.tbm_sessions && d.tbm_sessions.length > 0) { html += d.tbm_sessions.map(s => { const proxyTag = s.is_proxy_input ? ` · 대리입력(${escHtml(s.proxy_input_by_name || '-')})` : ''; return `
세션 #${s.session_id} · ${s.status === 'completed' ? '완료' : '진행중'} · 리더: ${escHtml(s.leader_name || '-')}${proxyTag}
`; }).join(''); } else { html += '
세션 없음
'; } html += '
'; // 작업보고서 섹션 html += '
작업보고서
'; if (d.work_reports && d.work_reports.length > 0) { html += d.work_reports.map(r => `
${escHtml(r.project_name || '-')} · ${escHtml(r.work_type_name || '-')} · ${r.work_hours || 0}시간
`).join(''); } else { html += '
보고서 없음
'; } html += '
'; bodyEl.innerHTML = html; // 완료 상태면 대리입력 버튼 숨김 const btn = document.getElementById('sheetProxyBtn'); btn.style.display = workerBasic.status === 'complete' ? 'none' : 'block'; } catch (e) { bodyEl.innerHTML = '
네트워크 오류
'; } } function closeSheet() { document.getElementById('detailSheet').classList.remove('open'); setTimeout(() => { document.getElementById('sheetOverlay').classList.add('hidden'); document.getElementById('detailSheet').classList.add('hidden'); }, 300); } // ===== Navigation ===== function goProxyInput() { location.href = '/pages/work/proxy-input.html?date=' + formatDateStr(currentDate); } function goProxyInputSingle() { if (selectedWorkerId) { location.href = '/pages/work/proxy-input.html?date=' + formatDateStr(currentDate) + '&user_id=' + selectedWorkerId; } }