Files
tk-factory-services/system1-factory/web/static/js/tkfb-dashboard.js
Hyungi Ahn 862a2683d3 feat(tkuser): 탭 카테고리 그룹핑 + 설비 관리 탭 추가 + tkfb admin 페이지 통합
- tkuser 탭을 5개 카테고리로 그룹핑 (인력/현장/업무/거래/시스템)
- 설비 관리 탭 신규 추가 (CRUD, 필터, 상세 보기)
- tkfb 사이드바 admin 메뉴 6개를 tkuser 외부 링크로 교체
- tkfb admin HTML 6개를 tkuser 리다이렉트로 변경
- gateway 알림 벨 링크를 tkuser로 변경
- _tkuserBase 헬퍼로 개발/운영 환경 자동 분기

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:35:24 +09:00

128 lines
5.7 KiB
JavaScript

/* ===== Dashboard (대시보드) ===== */
const today = new Date().toISOString().substring(0, 10);
function updateDateTime() {
const now = new Date();
const days = ['일', '월', '화', '수', '목', '금', '토'];
const h = String(now.getHours()).padStart(2, '0');
const m = String(now.getMinutes()).padStart(2, '0');
const el = document.getElementById('dateTimeDisplay');
if (el) el.textContent = `${now.getFullYear()}${now.getMonth()+1}${now.getDate()}일 (${days[now.getDay()]}) ${h}:${m}`;
}
async function loadDashboard() {
updateDateTime();
const results = await Promise.allSettled([
api('/tbm/sessions/date/' + today).catch(() => ({ data: [] })),
api('/notifications/unread').catch(() => ({ data: [] })),
api('/equipments/repair-requests?status=pending').catch(() => ({ data: [] })),
api('/attendance/today-summary').catch(() => ({ data: {} })),
]);
const tbmData = results[0].status === 'fulfilled' ? results[0].value : { data: [] };
const notifData = results[1].status === 'fulfilled' ? results[1].value : { data: [] };
const repairData = results[2].status === 'fulfilled' ? results[2].value : { data: [] };
const attendData = results[3].status === 'fulfilled' ? results[3].value : { data: {} };
const tbmSessions = tbmData.data || [];
const notifications = notifData.data || [];
const repairs = repairData.data || [];
const attendance = attendData.data || {};
// Stats
document.getElementById('statTbm').textContent = tbmSessions.length;
document.getElementById('statWorkers').textContent = attendance.checked_in_count || 0;
document.getElementById('statRepairs').textContent = repairs.length;
document.getElementById('statNotifications').textContent = notifications.length;
// TBM list
renderTbmList(tbmSessions);
renderNotificationList(notifications);
renderRepairList(repairs);
}
function renderTbmList(sessions) {
const el = document.getElementById('tbmList');
if (!sessions.length) {
el.innerHTML = '<p class="text-gray-400 text-sm text-center py-4">금일 TBM이 없습니다</p>';
return;
}
el.innerHTML = sessions.slice(0, 5).map(s => {
const workers = s.team_member_count || 0;
return `<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<div class="text-sm font-medium text-gray-800">${escapeHtml(s.workplace_name || s.session_title || 'TBM')}</div>
<div class="text-xs text-gray-500">${escapeHtml(s.leader_name || '-')} · ${workers}명</div>
</div>
<span class="badge ${s.status === 'completed' ? 'badge-green' : 'badge-amber'}">${s.status === 'completed' ? '완료' : '진행중'}</span>
</div>`;
}).join('');
if (sessions.length > 5) {
el.innerHTML += `<a href="/pages/work/tbm.html" class="block text-center text-xs text-orange-600 hover:text-orange-700 mt-2">전체 보기 (${sessions.length}건)</a>`;
}
}
function renderNotificationList(notifications) {
const el = document.getElementById('notificationList');
if (!notifications.length) {
el.innerHTML = '<p class="text-gray-400 text-sm text-center py-4">새 알림이 없습니다</p>';
return;
}
const icons = { repair: 'fa-wrench text-amber-500', safety: 'fa-shield-alt text-red-500', system: 'fa-bell text-blue-500', equipment: 'fa-cog text-gray-500', maintenance: 'fa-tools text-green-500' };
el.innerHTML = notifications.slice(0, 5).map(n => {
const iconClass = icons[n.type] || 'fa-bell text-gray-400';
return `<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100" onclick="location.href='${_tkuserBase}/?tab=notificationRecipients'">
<i class="fas ${iconClass} mt-0.5"></i>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-gray-800 truncate">${escapeHtml(n.title)}</div>
<div class="text-xs text-gray-500 mt-0.5">${formatTimeAgo(n.created_at)}</div>
</div>
</div>`;
}).join('');
}
function renderRepairList(repairs) {
const el = document.getElementById('repairList');
if (!repairs.length) {
el.innerHTML = '<p class="text-gray-400 text-sm text-center py-4">대기 중인 수리 요청이 없습니다</p>';
return;
}
el.innerHTML = repairs.slice(0, 5).map(r => {
return `<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<div class="text-sm font-medium text-gray-800">${escapeHtml(r.equipment_name || r.title || '수리 요청')}</div>
<div class="text-xs text-gray-500">${formatDate(r.created_at)}</div>
</div>
<span class="badge badge-red">대기</span>
</div>`;
}).join('');
if (repairs.length > 5) {
el.innerHTML += `<a href="/pages/admin/repair-management.html" class="block text-center text-xs text-orange-600 hover:text-orange-700 mt-2">전체 보기 (${repairs.length}건)</a>`;
}
}
function formatTimeAgo(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
const now = new Date();
const diff = now - d;
const mins = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (mins < 1) return '방금 전';
if (mins < 60) return `${mins}분 전`;
if (hours < 24) return `${hours}시간 전`;
if (days < 7) return `${days}일 전`;
return formatDate(dateStr);
}
/* ===== Init ===== */
(async function() {
if (!await initAuth()) return;
updateDateTime();
setInterval(updateDateTime, 60000);
loadDashboard();
})();