상단 헤더 바 제거, 프로필 카드 우측 상단에 로그아웃 버튼 배치. 대시보드 페이지에서 깔끔한 레이아웃 유지. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
171 lines
6.9 KiB
JavaScript
171 lines
6.9 KiB
JavaScript
/**
|
|
* 생산팀 대시보드 — 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.my_vacation_info': 'fa-info-circle',
|
|
'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',
|
|
'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 CATEGORY_COLORS = {
|
|
'작업 관리': '#3b82f6',
|
|
'공장 관리': '#f59e0b',
|
|
'소모품 관리': '#10b981',
|
|
'근태 관리': '#8b5cf6',
|
|
'시스템 관리': '#6b7280',
|
|
};
|
|
const DEFAULT_COLOR = '#06b6d4';
|
|
|
|
function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
|
|
async function initDashboard() {
|
|
showSkeleton();
|
|
try {
|
|
const result = await api('/dashboard/my-summary');
|
|
if (!result.success) throw new Error(result.message || '데이터 로드 실패');
|
|
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 / vacTotal) * 100) : 0;
|
|
const vacColor = vacRemaining >= 5 ? 'green' : vacRemaining >= 3 ? 'yellow' : 'red';
|
|
|
|
const otHours = overtime.total_overtime_hours;
|
|
const otDays = overtime.overtime_days;
|
|
|
|
card.innerHTML = `
|
|
<button onclick="doLogout()" class="pd-logout-btn" title="로그아웃">
|
|
<i class="fas fa-sign-out-alt"></i>
|
|
</button>
|
|
<div class="pd-profile-header">
|
|
<div class="pd-avatar">${escHtml(initial)}</div>
|
|
<div>
|
|
<div class="pd-profile-name">${escHtml(user.worker_name || user.name)}</div>
|
|
<div class="pd-profile-sub">${escHtml(user.job_type || '')}${user.job_type ? ' · ' : ''}${escHtml(user.department_name)}</div>
|
|
</div>
|
|
</div>
|
|
<div class="pd-stats-row">
|
|
<div class="pd-stat-card">
|
|
<div class="pd-stat-label"><i class="fas fa-umbrella-beach" style="font-size:12px"></i> 연차</div>
|
|
${vacTotal > 0 ? `
|
|
<div class="pd-stat-value">잔여 ${vacRemaining}일</div>
|
|
<div class="pd-stat-sub">${vacTotal}일 중 ${vacUsed}일 사용</div>
|
|
<div class="pd-progress-bar"><div class="pd-progress-fill pd-progress-${vacColor}" style="width:${vacPct}%"></div></div>
|
|
` : `<div class="pd-stat-value" style="font-size:14px;opacity:0.7">연차 정보 미등록</div>`}
|
|
</div>
|
|
<div class="pd-stat-card">
|
|
<div class="pd-stat-label"><i class="fas fa-clock" style="font-size:12px"></i> 연장근로</div>
|
|
<div class="pd-stat-value">${otHours.toFixed(1)}h</div>
|
|
<div class="pd-stat-sub">이번달 ${otDays}일</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 아이콘 그리드
|
|
renderGrid('deptPagesGrid', 'deptPagesSection', quick_access.department_pages);
|
|
renderGrid('personalPagesGrid', 'personalPagesSection', quick_access.personal_pages);
|
|
renderGrid('adminPagesGrid', 'adminPagesSection', quick_access.admin_pages);
|
|
}
|
|
|
|
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');
|
|
|
|
// dashboard 자체 제외
|
|
const filtered = pages.filter(p => p.page_key !== 'dashboard');
|
|
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 `<a href="${escHtml(p.page_path)}" class="pd-grid-item">
|
|
<div class="pd-grid-icon" style="background:${color}">
|
|
<i class="fas ${icon}"></i>
|
|
</div>
|
|
<span class="pd-grid-label">${escHtml(p.page_name)}</span>
|
|
</a>`;
|
|
}).join('');
|
|
}
|
|
|
|
function showSkeleton() {
|
|
const card = document.getElementById('profileCard');
|
|
card.innerHTML = `
|
|
<div class="pd-profile-header">
|
|
<div class="pd-skeleton" style="width:48px;height:48px;border-radius:50%"></div>
|
|
<div style="flex:1">
|
|
<div class="pd-skeleton" style="width:100px;height:18px;margin-bottom:6px"></div>
|
|
<div class="pd-skeleton" style="width:140px;height:14px"></div>
|
|
</div>
|
|
</div>
|
|
<div class="pd-stats-row">
|
|
<div class="pd-skeleton" style="height:90px"></div>
|
|
<div class="pd-skeleton" style="height:90px"></div>
|
|
</div>
|
|
`;
|
|
// 그리드 스켈레톤
|
|
['deptPagesGrid'].forEach(id => {
|
|
const g = document.getElementById(id);
|
|
if (g) g.innerHTML = Array(8).fill('<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div class="pd-skeleton" style="width:52px;height:52px;border-radius:14px"></div><div class="pd-skeleton" style="width:40px;height:12px"></div></div>').join('');
|
|
});
|
|
}
|
|
|
|
function showError(msg) {
|
|
document.getElementById('profileCard').innerHTML = `
|
|
<div class="pd-error">
|
|
<i class="fas fa-exclamation-circle"></i>
|
|
<p>${escHtml(msg || '정보를 불러올 수 없습니다.')}</p>
|
|
<button class="pd-error-btn" onclick="initDashboard()"><i class="fas fa-redo mr-1"></i>새로고침</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// tkfb-core.js 인증 완료 후 실행
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => setTimeout(initDashboard, 300));
|
|
} else {
|
|
setTimeout(initDashboard, 300);
|
|
}
|