/** * 생산팀 대시보드 — Sprint 003 */ const PAGE_ICONS = { 'dashboard': 'fa-home', 'work.tbm': 'fa-clipboard-list', 'work.report_create': 'fa-file-alt', 'work.analysis': 'fa-chart-bar', 'work.nonconformity': 'fa-exclamation-triangle', 'work.schedule': 'fa-calendar-alt', 'work.meetings': 'fa-users', 'work.daily_status': 'fa-chart-bar', 'work.proxy_input': 'fa-user-edit', 'factory.repair_management': 'fa-tools', 'inspection.daily_patrol': 'fa-route', 'inspection.checkin': 'fa-user-check', 'inspection.work_status': 'fa-briefcase', 'purchase.request': 'fa-shopping-cart', 'purchase.analysis': 'fa-chart-line', 'attendance.monthly': 'fa-calendar', 'attendance.vacation_request': 'fa-paper-plane', 'attendance.vacation_management': 'fa-cog', 'attendance.vacation_allocation': 'fa-plus-circle', 'attendance.annual_overview': 'fa-chart-pie', 'attendance.monthly_comparison': 'fa-scale-balanced', 'admin.user_management': 'fa-users-cog', 'admin.projects': 'fa-project-diagram', 'admin.tasks': 'fa-tasks', 'admin.workplaces': 'fa-building', 'admin.equipments': 'fa-cogs', 'admin.departments': 'fa-sitemap', 'admin.notifications': 'fa-bell', 'admin.attendance_report': 'fa-clipboard-check', }; // 내 메뉴에서 제외 (대시보드에서 직접 확인) const HIDDEN_PAGES = ['dashboard', 'attendance.my_vacation_info']; const CATEGORY_COLORS = { '작업 관리': '#3b82f6', '공장 관리': '#f59e0b', '소모품 관리': '#10b981', '근태 관리': '#8b5cf6', '시스템 관리': '#6b7280', }; const DEFAULT_COLOR = '#06b6d4'; function isExpired(expiresAt) { if (!expiresAt) return false; const today = new Date(); today.setHours(0,0,0,0); const exp = new Date(expiresAt); exp.setHours(0,0,0,0); return today > exp; } function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } function fmtDays(n) { return n % 1 === 0 ? n.toString() : n.toFixed(1); } let _dashboardData = null; async function initDashboard() { showSkeleton(); try { const result = await api('/dashboard/my-summary'); if (!result.success) throw new Error(result.message || '데이터 로드 실패'); _dashboardData = result.data; renderDashboard(result.data); } catch (err) { showError(err.message); } } function renderDashboard(data) { const { user, vacation, overtime, quick_access } = data; const card = document.getElementById('profileCard'); const initial = (user.worker_name || user.name || '?').charAt(0); const vacRemaining = vacation.remaining_days; const vacTotal = vacation.total_days; const vacUsed = vacation.used_days; const vacPct = vacTotal > 0 ? Math.round((vacUsed / Math.max(vacTotal, 1)) * 100) : 0; const vacColor = vacRemaining >= 5 ? 'green' : vacRemaining >= 3 ? 'yellow' : 'red'; const otHours = overtime.total_overtime_hours; const otDays = overtime.overtime_days; card.innerHTML = `
${escHtml(initial)}
${escHtml(user.worker_name || user.name)}
${escHtml(user.job_type || '')}${user.job_type ? ' · ' : ''}${escHtml(user.department_name)} 비밀번호 변경
연차
${vacTotal > 0 ? `
잔여 ${fmtDays(vacRemaining)}일 / ${fmtDays(vacTotal)}일
` : `
미등록
`}
${vacTotal > 0 ? `
` : ''}
연장근로
${otHours.toFixed(1)}h 이번달 ${otDays}일
`; renderGrid('deptPagesGrid', 'deptPagesSection', quick_access.department_pages); renderGrid('personalPagesGrid', 'personalPagesSection', quick_access.personal_pages); renderGrid('adminPagesGrid', 'adminPagesSection', quick_access.admin_pages); } function openVacDetailModal() { if (!_dashboardData) return; const { vacation } = _dashboardData; const details = vacation.details || []; const groups = {}; details.forEach(d => { const bt = d.balance_type || 'AUTO'; if (!groups[bt]) groups[bt] = { total: 0, used: 0, remaining: 0, expires_at: d.expires_at, items: [] }; groups[bt].total += d.total; groups[bt].used += d.used; groups[bt].remaining += d.remaining; if (d.expires_at) groups[bt].expires_at = d.expires_at; groups[bt].items.push(d); }); const LABELS = { CARRY_OVER: '이월연차', AUTO: '정기연차', MANUAL: '추가부여', LONG_SERVICE: '장기근속', COMPANY_GRANT: '경조사/특별' }; const ORDER = ['CARRY_OVER', 'AUTO', 'MANUAL', 'LONG_SERVICE', 'COMPANY_GRANT']; let html = ''; ORDER.forEach(bt => { const g = groups[bt]; if (!g || (g.total === 0 && g.used === 0)) return; const label = LABELS[bt] || bt; const expired = bt === 'CARRY_OVER' && isExpired(g.expires_at); const lapsed = expired ? Math.max(0, g.total - g.used) : 0; html += `
${label} ${g.total !== 0 ? `배정 ${fmtDays(g.total)}` : ''} ${g.used > 0 ? ` · 사용 ${fmtDays(g.used)}` : ''} ${expired && lapsed > 0 ? ` · 만료 ${fmtDays(lapsed)}` : ''} ${!expired && g.remaining !== 0 ? ` · 잔여 ${fmtDays(g.remaining)}` : ''}
`; }); if (!html) html = '
연차 정보가 없습니다
'; html += `
합계 배정 ${fmtDays(vacation.total_days)} · 사용 ${fmtDays(vacation.used_days)} · 잔여 ${fmtDays(vacation.remaining_days)}
`; document.getElementById('vacDetailContent').innerHTML = html; document.getElementById('vacDetailModal').classList.add('active'); } function closeVacDetail() { document.getElementById('vacDetailModal').classList.remove('active'); } function renderGrid(gridId, sectionId, pages) { const grid = document.getElementById(gridId); const section = document.getElementById(sectionId); if (!pages || pages.length === 0) { section.classList.add('hidden'); return; } section.classList.remove('hidden'); const filtered = pages.filter(p => !HIDDEN_PAGES.includes(p.page_key)); if (filtered.length === 0) { section.classList.add('hidden'); return; } grid.innerHTML = filtered.map(p => { const icon = PAGE_ICONS[p.page_key] || p.icon || 'fa-circle'; const color = CATEGORY_COLORS[p.category] || DEFAULT_COLOR; return `
${escHtml(p.page_name)}
`; }).join(''); } function showSkeleton() { const card = document.getElementById('profileCard'); card.innerHTML = `
`; ['deptPagesGrid'].forEach(id => { const g = document.getElementById(id); if (g) g.innerHTML = Array(8).fill('
').join(''); }); } function showError(msg) { document.getElementById('profileCard').innerHTML = `

${escHtml(msg || '정보를 불러올 수 없습니다.')}

`; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => setTimeout(initDashboard, 300)); } else { setTimeout(initDashboard, 300); }