Files
tk-factory-services/tksafety/web/static/js/tksafety-entry-dashboard.js
Hyungi Ahn 6a20056e05 feat(tksafety): 통합 출입신고 관리 시스템 구현
- DB 마이그레이션: request_type, visitor_name, department_id, check_in/out_time 컬럼 + status ENUM 확장
- 4소스 UNION 대시보드: 방문(외부/내부) + TBM + 협력업체 통합 조회
- 체크인/체크아웃 API + 내부 출입 신고(승인 불필요) 지원
- 통합 출입 현황판 페이지 신규 (entry-dashboard.html)
- 출입 신청/관리 페이지에 유형 필터 + 체크인/아웃 버튼 추가
- safety_entry_dashboard 권한 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:24:13 +09:00

137 lines
4.9 KiB
JavaScript

/* ===== Entry Dashboard (출입 현황판) ===== */
let dashboardData = [];
let currentSourceFilter = '';
let refreshTimer = null;
/* ===== Source/Status badges ===== */
function sourceBadge(s) {
const m = {
tbm: ['badge-blue', 'TBM'],
partner: ['badge-green', '협력업체'],
visit: ['badge-amber', '방문']
};
const [cls, label] = m[s] || ['badge-gray', s];
return `<span class="badge ${cls}">${label}</span>`;
}
function entryStatusBadge(s) {
const m = {
checked_in: ['badge-blue', '체크인'],
checked_out: ['badge-gray', '체크아웃'],
approved: ['badge-green', '승인'],
training_completed: ['badge-blue', '교육완료'],
absent: ['badge-red', '불참']
};
const [cls, label] = m[s] || ['badge-gray', s];
return `<span class="badge ${cls}">${label}</span>`;
}
/* ===== Load dashboard data ===== */
async function loadDashboard() {
const date = document.getElementById('dashboardDate').value;
try {
const [dashRes, statsRes] = await Promise.all([
api('/visit-requests/entry-dashboard?date=' + date),
api('/visit-requests/entry-dashboard/stats?date=' + date)
]);
dashboardData = dashRes.data || [];
updateStats(statsRes.data || {});
renderDashboard();
} catch (e) {
showToast('대시보드 로드 실패: ' + e.message, 'error');
}
}
function updateStats(stats) {
const total = stats.external_visit + stats.internal_visit + stats.partner + stats.tbm;
document.getElementById('statTotal').textContent = total;
document.getElementById('statTbm').textContent = stats.tbm;
document.getElementById('statPartner').textContent = stats.partner;
document.getElementById('statExternal').textContent = stats.external_visit;
document.getElementById('statInternal').textContent = stats.internal_visit;
}
/* ===== Render table ===== */
function renderDashboard() {
const tbody = document.getElementById('dashboardBody');
const filtered = currentSourceFilter
? dashboardData.filter(r => r.source === currentSourceFilter)
: dashboardData;
if (!filtered.length) {
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-gray-400 py-8">데이터가 없습니다</td></tr>';
return;
}
tbody.innerHTML = filtered.map(r => {
const name = escapeHtml(r.visitor_name || '-');
const org = escapeHtml(r.visitor_company || '-');
const workplace = escapeHtml(r.workplace_name || '-');
const inTime = r.check_in_time ? String(r.check_in_time).substring(11, 16) : (r.entry_time ? String(r.entry_time).substring(0, 5) : '-');
const outTime = r.check_out_time ? String(r.check_out_time).substring(11, 16) : '-';
const purpose = escapeHtml(r.purpose_name || '-');
const note = r.source_note ? `<span class="text-xs text-gray-500 italic">${escapeHtml(r.source_note)}</span>` : '';
const count = r.visitor_count > 1 ? ` <span class="text-xs text-gray-400">(${r.visitor_count}명)</span>` : '';
return `<tr>
<td>${sourceBadge(r.source)}</td>
<td>${name}${count}</td>
<td>${org}</td>
<td>${workplace}</td>
<td>${inTime}</td>
<td>${outTime}</td>
<td>${purpose}</td>
<td>${entryStatusBadge(r.status)}</td>
<td class="hide-mobile">${note}</td>
</tr>`;
}).join('');
}
/* ===== Tab filter ===== */
function filterSource(source) {
currentSourceFilter = source;
document.querySelectorAll('.source-tab').forEach(t => {
t.classList.toggle('active', t.dataset.source === source);
});
renderDashboard();
}
/* ===== Auto refresh ===== */
function setupAutoRefresh() {
const cb = document.getElementById('autoRefresh');
cb.addEventListener('change', () => {
if (cb.checked) startAutoRefresh();
else stopAutoRefresh();
});
startAutoRefresh();
}
function startAutoRefresh() {
stopAutoRefresh();
refreshTimer = setInterval(loadDashboard, 180000); // 3분
}
function stopAutoRefresh() {
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
}
/* ===== Init ===== */
function initEntryDashboard() {
if (!initAuth()) return;
const today = new Date().toISOString().substring(0, 10);
document.getElementById('dashboardDate').value = today;
document.getElementById('dashboardDate').addEventListener('change', loadDashboard);
// Source tab styling
const style = document.createElement('style');
style.textContent = `.source-tab { border-bottom-color: transparent; color: #6b7280; cursor: pointer; }
.source-tab:hover { color: #374151; background: #f9fafb; }
.source-tab.active { border-bottom-color: #2563eb; color: #2563eb; font-weight: 600; }`;
document.head.appendChild(style);
loadDashboard();
setupAutoRefresh();
}