feat: 3-System 분리 프로젝트 초기 코드 작성
TK-FB(공장관리+신고)와 M-Project(부적합관리)를 3개 독립 시스템으로 분리하기 위한 전체 코드 구조 작성. - SSO 인증 서비스 (bcrypt + pbkdf2 이중 해시 지원) - System 1: 공장관리 (TK-FB 기반, 신고 코드 제거) - System 2: 신고 (TK-FB에서 workIssue 코드 추출) - System 3: 부적합관리 (M-Project 기반) - Gateway 포털 (path-based 라우팅) - 통합 docker-compose.yml 및 배포 스크립트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
325
system1-factory/web/js/tbm/index.js
Normal file
325
system1-factory/web/js/tbm/index.js
Normal file
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* TBM - Module Loader
|
||||
* TBM 모듈을 초기화하고 연결하는 메인 진입점
|
||||
*
|
||||
* 로드 순서:
|
||||
* 1. state.js - 전역 상태 관리
|
||||
* 2. utils.js - 유틸리티 함수
|
||||
* 3. api.js - API 클라이언트
|
||||
* 4. index.js - 이 파일 (메인 컨트롤러)
|
||||
*/
|
||||
|
||||
class TbmController {
|
||||
constructor() {
|
||||
this.state = window.TbmState;
|
||||
this.api = window.TbmAPI;
|
||||
this.utils = window.TbmUtils;
|
||||
this.initialized = false;
|
||||
|
||||
console.log('[TbmController] 생성');
|
||||
}
|
||||
|
||||
/**
|
||||
* 초기화
|
||||
*/
|
||||
async init() {
|
||||
if (this.initialized) {
|
||||
console.log('[TbmController] 이미 초기화됨');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🛠️ TBM 관리 페이지 초기화');
|
||||
|
||||
// API 함수가 로드될 때까지 대기
|
||||
let retryCount = 0;
|
||||
while (!window.apiCall && retryCount < 50) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
retryCount++;
|
||||
}
|
||||
|
||||
if (!window.apiCall) {
|
||||
window.showToast?.('시스템을 초기화할 수 없습니다. 페이지를 새로고침해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 오늘 날짜 설정 (서울 시간대 기준)
|
||||
const today = this.utils.getTodayKST();
|
||||
const tbmDateEl = document.getElementById('tbmDate');
|
||||
const sessionDateEl = document.getElementById('sessionDate');
|
||||
if (tbmDateEl) tbmDateEl.value = today;
|
||||
if (sessionDateEl) sessionDateEl.value = today;
|
||||
|
||||
// 이벤트 리스너 설정
|
||||
this.setupEventListeners();
|
||||
|
||||
// 초기 데이터 로드
|
||||
await this.api.loadInitialData();
|
||||
await this.api.loadTodayOnlyTbm();
|
||||
|
||||
// 렌더링
|
||||
this.displayTodayTbmSessions();
|
||||
|
||||
this.initialized = true;
|
||||
console.log('[TbmController] 초기화 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
* 이벤트 리스너 설정
|
||||
*/
|
||||
setupEventListeners() {
|
||||
// 탭 버튼들
|
||||
document.querySelectorAll('.tbm-tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tabName = btn.dataset.tab;
|
||||
if (tabName) this.switchTbmTab(tabName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 탭 전환
|
||||
*/
|
||||
async switchTbmTab(tabName) {
|
||||
this.state.setCurrentTab(tabName);
|
||||
|
||||
// 탭 버튼 활성화 상태 변경
|
||||
document.querySelectorAll('.tbm-tab-btn').forEach(btn => {
|
||||
if (btn.dataset.tab === tabName) {
|
||||
btn.classList.add('active');
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// 탭 컨텐츠 표시 변경
|
||||
document.querySelectorAll('.tbm-tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
const tabContent = document.getElementById(`${tabName}-tab`);
|
||||
if (tabContent) tabContent.classList.add('active');
|
||||
|
||||
// 탭에 따라 데이터 로드
|
||||
if (tabName === 'tbm-input') {
|
||||
await this.api.loadTodayOnlyTbm();
|
||||
this.displayTodayTbmSessions();
|
||||
} else if (tabName === 'tbm-manage') {
|
||||
await this.api.loadRecentTbmGroupedByDate();
|
||||
this.displayTbmGroupedByDate();
|
||||
this.updateViewModeIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 오늘의 TBM 세션 표시
|
||||
*/
|
||||
displayTodayTbmSessions() {
|
||||
const grid = document.getElementById('todayTbmGrid');
|
||||
const emptyState = document.getElementById('todayEmptyState');
|
||||
const todayTotalEl = document.getElementById('todayTotalSessions');
|
||||
const todayCompletedEl = document.getElementById('todayCompletedSessions');
|
||||
const todayActiveEl = document.getElementById('todayActiveSessions');
|
||||
|
||||
const sessions = this.state.todaySessions;
|
||||
|
||||
if (sessions.length === 0) {
|
||||
if (grid) grid.innerHTML = '';
|
||||
if (emptyState) emptyState.style.display = 'flex';
|
||||
if (todayTotalEl) todayTotalEl.textContent = '0';
|
||||
if (todayCompletedEl) todayCompletedEl.textContent = '0';
|
||||
if (todayActiveEl) todayActiveEl.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
if (emptyState) emptyState.style.display = 'none';
|
||||
|
||||
const completedCount = sessions.filter(s => s.status === 'completed').length;
|
||||
const activeCount = sessions.filter(s => s.status === 'draft').length;
|
||||
|
||||
if (todayTotalEl) todayTotalEl.textContent = sessions.length;
|
||||
if (todayCompletedEl) todayCompletedEl.textContent = completedCount;
|
||||
if (todayActiveEl) todayActiveEl.textContent = activeCount;
|
||||
|
||||
if (grid) {
|
||||
grid.innerHTML = sessions.map(session => this.createSessionCard(session)).join('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 날짜별 그룹으로 TBM 표시
|
||||
*/
|
||||
displayTbmGroupedByDate() {
|
||||
const container = document.getElementById('tbmDateGroupsContainer');
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
const totalSessionsEl = document.getElementById('totalSessions');
|
||||
const completedSessionsEl = document.getElementById('completedSessions');
|
||||
|
||||
if (!container) return;
|
||||
|
||||
const sortedDates = Object.keys(this.state.dateGroupedSessions).sort((a, b) =>
|
||||
new Date(b) - new Date(a)
|
||||
);
|
||||
|
||||
if (sortedDates.length === 0 || this.state.allLoadedSessions.length === 0) {
|
||||
container.innerHTML = '';
|
||||
if (emptyState) emptyState.style.display = 'flex';
|
||||
if (totalSessionsEl) totalSessionsEl.textContent = '0';
|
||||
if (completedSessionsEl) completedSessionsEl.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
if (emptyState) emptyState.style.display = 'none';
|
||||
|
||||
// 통계 업데이트
|
||||
const completedCount = this.state.allLoadedSessions.filter(s => s.status === 'completed').length;
|
||||
if (totalSessionsEl) totalSessionsEl.textContent = this.state.allLoadedSessions.length;
|
||||
if (completedSessionsEl) completedSessionsEl.textContent = completedCount;
|
||||
|
||||
// 날짜별 그룹 HTML 생성
|
||||
const today = this.utils.getTodayKST();
|
||||
const dayNames = ['일', '월', '화', '수', '목', '금', '토'];
|
||||
|
||||
container.innerHTML = sortedDates.map(date => {
|
||||
const sessions = this.state.dateGroupedSessions[date];
|
||||
const dateObj = new Date(date + 'T00:00:00');
|
||||
const dayName = dayNames[dateObj.getDay()];
|
||||
const isToday = date === today;
|
||||
|
||||
const [year, month, day] = date.split('-');
|
||||
const displayDate = `${parseInt(month)}월 ${parseInt(day)}일`;
|
||||
|
||||
return `
|
||||
<div class="tbm-date-group" data-date="${date}">
|
||||
<div class="tbm-date-header ${isToday ? 'today' : ''}" onclick="toggleDateGroup('${date}')">
|
||||
<span class="tbm-date-toggle">▼</span>
|
||||
<span class="tbm-date-title">${displayDate}</span>
|
||||
<span class="tbm-date-day">${dayName}요일</span>
|
||||
${isToday ? '<span class="tbm-today-badge">오늘</span>' : ''}
|
||||
<span class="tbm-date-count">${sessions.length}건</span>
|
||||
</div>
|
||||
<div class="tbm-date-content">
|
||||
<div class="tbm-date-grid">
|
||||
${sessions.map(session => this.createSessionCard(session)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 뷰 모드 표시 업데이트
|
||||
*/
|
||||
updateViewModeIndicator() {
|
||||
const indicator = document.getElementById('viewModeIndicator');
|
||||
const text = document.getElementById('viewModeText');
|
||||
|
||||
if (indicator && text) {
|
||||
if (this.state.isAdminUser()) {
|
||||
indicator.style.display = 'none';
|
||||
} else {
|
||||
indicator.style.display = 'inline-flex';
|
||||
text.textContent = '내 TBM';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 세션 카드 생성
|
||||
*/
|
||||
createSessionCard(session) {
|
||||
const statusBadge = this.utils.getStatusBadge(session.status);
|
||||
|
||||
const leaderName = session.leader_name || session.created_by_name || '작업 책임자';
|
||||
const leaderRole = session.leader_name
|
||||
? (session.leader_job_type || '작업자')
|
||||
: '관리자';
|
||||
|
||||
return `
|
||||
<div class="tbm-session-card" onclick="viewTbmSession(${session.session_id})">
|
||||
<div class="tbm-card-header">
|
||||
<div class="tbm-card-header-top">
|
||||
<div>
|
||||
<h3 class="tbm-card-leader">
|
||||
${leaderName}
|
||||
<span class="tbm-card-leader-role">${leaderRole}</span>
|
||||
</h3>
|
||||
</div>
|
||||
${statusBadge}
|
||||
</div>
|
||||
<div class="tbm-card-date">
|
||||
<span>📅</span>
|
||||
${this.utils.formatDate(session.session_date)} ${session.start_time ? '| ' + session.start_time : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tbm-card-body">
|
||||
<div class="tbm-card-info-grid">
|
||||
<div class="tbm-card-info-item">
|
||||
<span class="tbm-card-info-label">프로젝트</span>
|
||||
<span class="tbm-card-info-value">${session.project_name || '-'}</span>
|
||||
</div>
|
||||
<div class="tbm-card-info-item">
|
||||
<span class="tbm-card-info-label">공정</span>
|
||||
<span class="tbm-card-info-value">${session.work_type_name || '-'}</span>
|
||||
</div>
|
||||
<div class="tbm-card-info-item">
|
||||
<span class="tbm-card-info-label">작업장</span>
|
||||
<span class="tbm-card-info-value">${session.work_location || '-'}</span>
|
||||
</div>
|
||||
<div class="tbm-card-info-item">
|
||||
<span class="tbm-card-info-label">팀원</span>
|
||||
<span class="tbm-card-info-value">${session.team_member_count || 0}명</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${session.status === 'draft' ? `
|
||||
<div class="tbm-card-footer">
|
||||
<button class="tbm-btn tbm-btn-primary tbm-btn-sm" onclick="event.stopPropagation(); openTeamCompositionModal(${session.session_id})">
|
||||
👥 팀 구성
|
||||
</button>
|
||||
<button class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="event.stopPropagation(); openSafetyCheckModal(${session.session_id})">
|
||||
✓ 안전 체크
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 디버그
|
||||
*/
|
||||
debug() {
|
||||
console.log('[TbmController] 상태 디버그:');
|
||||
this.state.debug();
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 인스턴스 생성
|
||||
window.TbmController = new TbmController();
|
||||
|
||||
// 하위 호환성: 기존 전역 함수들
|
||||
window.switchTbmTab = (tabName) => window.TbmController.switchTbmTab(tabName);
|
||||
window.displayTodayTbmSessions = () => window.TbmController.displayTodayTbmSessions();
|
||||
window.displayTbmGroupedByDate = () => window.TbmController.displayTbmGroupedByDate();
|
||||
window.displayTbmSessions = () => window.TbmController.displayTbmGroupedByDate();
|
||||
window.createSessionCard = (session) => window.TbmController.createSessionCard(session);
|
||||
window.updateViewModeIndicator = () => window.TbmController.updateViewModeIndicator();
|
||||
|
||||
// 날짜 그룹 토글
|
||||
window.toggleDateGroup = function(date) {
|
||||
const group = document.querySelector(`.tbm-date-group[data-date="${date}"]`);
|
||||
if (group) {
|
||||
group.classList.toggle('collapsed');
|
||||
}
|
||||
};
|
||||
|
||||
// DOMContentLoaded 이벤트에서 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
window.TbmController.init();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
console.log('[Module] tbm/index.js 로드 완료');
|
||||
Reference in New Issue
Block a user