Phase 1: tksafety에 출입신청/체크리스트 API·웹 추가, tkfb 안전 코드 삭제
Phase 2: 사용자 관리 페이지 삭제, API 축소, 알림 수신자 tkuser 이관
Phase 3: tkuser 권한 페이지 정의 업데이트
Phase 4: 전체 34개 페이지 Tailwind CSS + tkfb-core.js 전환,
미사용 CSS 20개·인프라 JS 10개·템플릿·컴포넌트 삭제
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
372 lines
12 KiB
JavaScript
372 lines
12 KiB
JavaScript
// mobile-dashboard.js - 모바일 대시보드 v2
|
|
// 공장별 카테고리 탭 → 작업장 리스트 → 작업장별 상태 요약
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
if (window.innerWidth > 768) return;
|
|
|
|
var today = new Date().toISOString().slice(0, 10);
|
|
|
|
// ==================== 캐시 변수 ====================
|
|
var categories = [];
|
|
var allWorkplaces = [];
|
|
var tbmByWorkplace = {};
|
|
var movedByWorkplace = {};
|
|
var issuesByWorkplace = {};
|
|
var workplacesByCategory = {};
|
|
|
|
// ==================== 유틸리티 ====================
|
|
// escapeHtml, waitForApi → api-base.js 전역 사용
|
|
|
|
// ==================== 데이터 그룹핑 ====================
|
|
|
|
function groupTbmByWorkplace(sessions) {
|
|
tbmByWorkplace = {};
|
|
if (!Array.isArray(sessions)) return;
|
|
sessions.forEach(function(s) {
|
|
var wpId = s.workplace_id;
|
|
if (!wpId) return;
|
|
if (!tbmByWorkplace[wpId]) {
|
|
tbmByWorkplace[wpId] = { taskCount: 0, totalWorkers: 0, sessions: [] };
|
|
}
|
|
tbmByWorkplace[wpId].taskCount++;
|
|
tbmByWorkplace[wpId].totalWorkers += (parseInt(s.team_member_count) || 0);
|
|
tbmByWorkplace[wpId].sessions.push(s);
|
|
});
|
|
}
|
|
|
|
function groupMovedByWorkplace(items) {
|
|
movedByWorkplace = {};
|
|
if (!Array.isArray(items)) return;
|
|
items.forEach(function(eq) {
|
|
var wpId = eq.current_workplace_id;
|
|
if (!wpId) return;
|
|
if (!movedByWorkplace[wpId]) {
|
|
movedByWorkplace[wpId] = { movedCount: 0, items: [] };
|
|
}
|
|
movedByWorkplace[wpId].movedCount++;
|
|
movedByWorkplace[wpId].items.push(eq);
|
|
});
|
|
}
|
|
|
|
function groupIssuesByWorkplace(issues) {
|
|
issuesByWorkplace = {};
|
|
if (!Array.isArray(issues)) return;
|
|
var activeStatuses = ['reported', 'received', 'in_progress'];
|
|
issues.forEach(function(issue) {
|
|
var wpId = issue.workplace_id;
|
|
if (!wpId) return;
|
|
if (activeStatuses.indexOf(issue.status) === -1) return;
|
|
if (!issuesByWorkplace[wpId]) {
|
|
issuesByWorkplace[wpId] = { activeCount: 0, items: [] };
|
|
}
|
|
issuesByWorkplace[wpId].activeCount++;
|
|
issuesByWorkplace[wpId].items.push(issue);
|
|
});
|
|
}
|
|
|
|
function groupWorkplacesByCategory(workplaces) {
|
|
workplacesByCategory = {};
|
|
if (!Array.isArray(workplaces)) return;
|
|
workplaces.forEach(function(wp) {
|
|
var catId = wp.category_id;
|
|
if (!catId) return;
|
|
if (!workplacesByCategory[catId]) {
|
|
workplacesByCategory[catId] = [];
|
|
}
|
|
workplacesByCategory[catId].push(wp);
|
|
});
|
|
}
|
|
|
|
// ==================== 렌더링 ====================
|
|
|
|
function renderCategoryTabs() {
|
|
var container = document.getElementById('mCategoryTabs');
|
|
if (!container || !categories.length) return;
|
|
|
|
var html = '';
|
|
categories.forEach(function(cat, idx) {
|
|
html += '<button class="md-cat-tab' + (idx === 0 ? ' active' : '') +
|
|
'" data-id="' + cat.category_id + '">' +
|
|
escapeHtml(cat.category_name) + '</button>';
|
|
});
|
|
|
|
// 전체 탭
|
|
html += '<button class="md-cat-tab" data-id="all">전체</button>';
|
|
|
|
container.innerHTML = html;
|
|
|
|
// 이벤트 바인딩
|
|
var tabs = container.querySelectorAll('.md-cat-tab');
|
|
tabs.forEach(function(tab) {
|
|
tab.addEventListener('click', function() {
|
|
tabs.forEach(function(t) { t.classList.remove('active'); });
|
|
tab.classList.add('active');
|
|
var catId = tab.getAttribute('data-id');
|
|
selectCategory(catId);
|
|
});
|
|
});
|
|
|
|
// 첫 번째 카테고리 자동 선택
|
|
if (categories.length > 0) {
|
|
selectCategory(String(categories[0].category_id));
|
|
}
|
|
}
|
|
|
|
function selectCategory(categoryId) {
|
|
var workplaces;
|
|
if (categoryId === 'all') {
|
|
workplaces = allWorkplaces.filter(function(wp) { return wp.is_active !== false; });
|
|
} else {
|
|
workplaces = (workplacesByCategory[categoryId] || []).filter(function(wp) {
|
|
return wp.is_active !== false;
|
|
});
|
|
}
|
|
renderWorkplaceList(workplaces);
|
|
}
|
|
|
|
function renderWorkplaceList(workplaces) {
|
|
var container = document.getElementById('mWorkplaceList');
|
|
if (!container) return;
|
|
|
|
if (!workplaces || workplaces.length === 0) {
|
|
container.innerHTML = '<div class="md-wp-empty-all">등록된 작업장이 없습니다.</div>';
|
|
return;
|
|
}
|
|
|
|
var html = '';
|
|
workplaces.forEach(function(wp) {
|
|
var wpId = wp.workplace_id;
|
|
var tbm = tbmByWorkplace[wpId];
|
|
var moved = movedByWorkplace[wpId];
|
|
var issues = issuesByWorkplace[wpId];
|
|
|
|
var hasAny = tbm || moved || issues;
|
|
|
|
html += '<div class="md-wp-card" data-wp-id="' + wpId + '">';
|
|
|
|
// 헤더 (클릭 영역)
|
|
html += '<div class="md-wp-header">';
|
|
html += '<h3 class="md-wp-name">' + escapeHtml(wp.workplace_name);
|
|
if (hasAny) {
|
|
html += '<span class="md-wp-toggle">▼</span>';
|
|
}
|
|
html += '</h3>';
|
|
|
|
if (!hasAny) {
|
|
html += '<p class="md-wp-no-activity">오늘 활동이 없습니다</p>';
|
|
} else {
|
|
html += '<div class="md-wp-stats">';
|
|
|
|
// TBM 작업
|
|
if (tbm) {
|
|
html += '<div class="md-wp-stat-row">' +
|
|
'<span class="md-wp-stat-icon">🛠</span>' +
|
|
'<span class="md-wp-stat-text">작업 ' + tbm.taskCount + '건 · ' + tbm.totalWorkers + '명</span>' +
|
|
'</div>';
|
|
}
|
|
|
|
// 신고 (미완료만)
|
|
if (issues && issues.activeCount > 0) {
|
|
html += '<div class="md-wp-stat-row md-wp-stat--warning">' +
|
|
'<span class="md-wp-stat-icon">⚠</span>' +
|
|
'<span class="md-wp-stat-text">신고 ' + issues.activeCount + '건</span>' +
|
|
'</div>';
|
|
}
|
|
|
|
// 이동설비
|
|
if (moved && moved.movedCount > 0) {
|
|
html += '<div class="md-wp-stat-row">' +
|
|
'<span class="md-wp-stat-icon">↔</span>' +
|
|
'<span class="md-wp-stat-text">이동설비 ' + moved.movedCount + '건</span>' +
|
|
'</div>';
|
|
}
|
|
|
|
html += '</div>';
|
|
}
|
|
|
|
html += '</div>'; // .md-wp-header
|
|
|
|
// 상세 영역 (활동 있는 카드만)
|
|
if (hasAny) {
|
|
html += '<div class="md-wp-detail">' + renderCardDetail(wpId) + '</div>';
|
|
}
|
|
|
|
html += '</div>'; // .md-wp-card
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
|
|
// 클릭 이벤트 바인딩
|
|
var cards = container.querySelectorAll('.md-wp-card[data-wp-id]');
|
|
cards.forEach(function(card) {
|
|
var wpId = card.getAttribute('data-wp-id');
|
|
var hasActivity = tbmByWorkplace[wpId] ||
|
|
movedByWorkplace[wpId] || issuesByWorkplace[wpId];
|
|
if (!hasActivity) return;
|
|
card.querySelector('.md-wp-header').addEventListener('click', function() {
|
|
toggleCard(wpId);
|
|
});
|
|
});
|
|
}
|
|
|
|
// ==================== 카드 확장/접기 ====================
|
|
|
|
function toggleCard(wpId) {
|
|
var allCards = document.querySelectorAll('.md-wp-card.expanded');
|
|
var targetCard = document.querySelector('.md-wp-card[data-wp-id="' + wpId + '"]');
|
|
if (!targetCard) return;
|
|
|
|
var isExpanded = targetCard.classList.contains('expanded');
|
|
|
|
// 다른 카드 모두 접기 (아코디언)
|
|
allCards.forEach(function(card) {
|
|
card.classList.remove('expanded');
|
|
});
|
|
|
|
// 토글
|
|
if (!isExpanded) {
|
|
targetCard.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
function renderCardDetail(wpId) {
|
|
var html = '';
|
|
var tbm = tbmByWorkplace[wpId];
|
|
var issues = issuesByWorkplace[wpId];
|
|
var moved = movedByWorkplace[wpId];
|
|
|
|
// TBM 작업
|
|
if (tbm && tbm.sessions.length > 0) {
|
|
html += '<div class="md-wp-detail-section">';
|
|
html += '<div class="md-wp-detail-title">▶ 작업</div>';
|
|
tbm.sessions.forEach(function(s) {
|
|
var taskName = s.task_name || '작업명 미지정';
|
|
var leaderName = s.leader_name || '미지정';
|
|
var memberCount = (parseInt(s.team_member_count) || 0);
|
|
html += '<div class="md-wp-detail-item">';
|
|
html += '<div class="md-wp-detail-main">' + escapeHtml(taskName) + '</div>';
|
|
html += '<div class="md-wp-detail-sub">' + escapeHtml(leaderName) + ' · ' + memberCount + '명</div>';
|
|
html += '</div>';
|
|
});
|
|
html += '</div>';
|
|
}
|
|
|
|
// 신고
|
|
if (issues && issues.items.length > 0) {
|
|
var statusMap = { reported: '신고', received: '접수', in_progress: '처리중' };
|
|
html += '<div class="md-wp-detail-section">';
|
|
html += '<div class="md-wp-detail-title">▶ 신고</div>';
|
|
issues.items.forEach(function(issue) {
|
|
var category = issue.issue_category_name || '미분류';
|
|
var desc = issue.additional_description || '';
|
|
if (desc.length > 30) desc = desc.substring(0, 30) + '...';
|
|
var statusText = statusMap[issue.status] || issue.status;
|
|
var statusClass = 'md-wp-issue-status--' + (issue.status || 'reported');
|
|
var reporter = issue.reporter_name || '';
|
|
var icon = issue.status === 'in_progress' ? '🔴' : '⚠';
|
|
html += '<div class="md-wp-detail-item">';
|
|
html += '<div class="md-wp-detail-main">' + icon + ' ' + escapeHtml(category);
|
|
if (desc) html += ' · ' + escapeHtml(desc);
|
|
html += '</div>';
|
|
html += '<div class="md-wp-detail-sub"><span class="md-wp-issue-status ' + statusClass + '">' + statusText + '</span>';
|
|
if (reporter) html += ' → ' + escapeHtml(reporter);
|
|
html += '</div>';
|
|
html += '</div>';
|
|
});
|
|
html += '</div>';
|
|
}
|
|
|
|
// 이동설비
|
|
if (moved && moved.items.length > 0) {
|
|
html += '<div class="md-wp-detail-section">';
|
|
html += '<div class="md-wp-detail-title">▶ 이동설비</div>';
|
|
moved.items.forEach(function(eq) {
|
|
var eqName = eq.equipment_name || '설비명 미지정';
|
|
var fromWp = eq.original_workplace_name || '?';
|
|
var toWp = eq.current_workplace_name || '?';
|
|
html += '<div class="md-wp-detail-item">';
|
|
html += '<div class="md-wp-detail-main">' + escapeHtml(eqName) + '</div>';
|
|
html += '<div class="md-wp-detail-sub">' + escapeHtml(fromWp) + ' → ' + escapeHtml(toWp) + '</div>';
|
|
html += '</div>';
|
|
});
|
|
html += '</div>';
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
// ==================== 초기화 ====================
|
|
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
try {
|
|
await waitForApi();
|
|
} catch (e) {
|
|
console.error('mobile-dashboard: apiCall not available');
|
|
return;
|
|
}
|
|
|
|
var view = document.getElementById('mobileDashboardView');
|
|
if (!view) return;
|
|
view.style.display = 'block';
|
|
|
|
// 날짜 표시
|
|
var now = new Date();
|
|
var days = ['일', '월', '화', '수', '목', '금', '토'];
|
|
var dateEl = document.getElementById('mDateValue');
|
|
if (dateEl) {
|
|
dateEl.textContent = now.getFullYear() + '.' +
|
|
String(now.getMonth() + 1).padStart(2, '0') + '.' +
|
|
String(now.getDate()).padStart(2, '0') + ' (' + days[now.getDay()] + ')';
|
|
}
|
|
|
|
// 로딩 표시
|
|
var listContainer = document.getElementById('mWorkplaceList');
|
|
if (listContainer) {
|
|
listContainer.innerHTML =
|
|
'<div class="md-skeleton"></div>' +
|
|
'<div class="md-skeleton" style="margin-top:8px;"></div>' +
|
|
'<div class="md-skeleton" style="margin-top:8px;"></div>';
|
|
}
|
|
|
|
// 데이터 병렬 로딩
|
|
var results = await Promise.allSettled([
|
|
window.apiCall('/workplaces/categories'),
|
|
window.apiCall('/tbm/sessions/date/' + today),
|
|
window.apiCall('/equipments/moved/list'),
|
|
window.apiCall('/work-issues?start_date=' + today + '&end_date=' + today),
|
|
window.apiCall('/workplaces')
|
|
]);
|
|
|
|
// 카테고리
|
|
if (results[0].status === 'fulfilled' && results[0].value && results[0].value.success) {
|
|
categories = results[0].value.data || [];
|
|
}
|
|
|
|
// TBM
|
|
if (results[1].status === 'fulfilled' && results[1].value && results[1].value.success) {
|
|
groupTbmByWorkplace(results[1].value.data || []);
|
|
}
|
|
|
|
// 이동설비
|
|
if (results[2].status === 'fulfilled' && results[2].value && results[2].value.success) {
|
|
groupMovedByWorkplace(results[2].value.data || []);
|
|
}
|
|
|
|
// 신고
|
|
if (results[3].status === 'fulfilled' && results[3].value && results[3].value.success) {
|
|
groupIssuesByWorkplace(results[3].value.data || []);
|
|
}
|
|
|
|
// 작업장 전체 (카테고리별 그룹핑)
|
|
if (results[4].status === 'fulfilled' && results[4].value && results[4].value.success) {
|
|
allWorkplaces = results[4].value.data || [];
|
|
groupWorkplacesByCategory(allWorkplaces);
|
|
}
|
|
|
|
// 렌더링
|
|
renderCategoryTabs();
|
|
});
|
|
})();
|