/**
* 생산팀 대시보드 — 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 = `
연차
${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);
}