/**
* TBM 모바일 위자드 - tbm-create.js
* 3단계 위자드로 TBM 세션을 생성하는 모바일 전용 페이지 로직
* Step 1: 작업자 선택, Step 2: 프로젝트+공정 선택, Step 3: 확인
* (작업/작업장은 생성 후 세부 편집 단계에서 입력)
*/
(function() {
'use strict';
// ==================== 위자드 상태 ====================
const W = {
step: 1,
totalSteps: 3,
sessionDate: null,
leaderId: null,
leaderName: '',
workers: new Set(), // user_id Set
workerNames: {}, // { user_id: worker_name }
projectId: null,
projectName: '',
workTypeId: null,
workTypeName: '',
showAddWorkType: false,
todayAssignments: null // 당일 배정 현황 캐시
};
const esc = window.escapeHtml || function(s) { return s || ''; };
// ==================== 초기화 ====================
document.addEventListener('DOMContentLoaded', async function() {
try {
// apiCall이 준비될 때까지 대기
await waitForApi();
// 초기 데이터 로드
await window.TbmAPI.loadInitialData();
// 기본 정보 자동 설정
W.sessionDate = window.TbmUtils.getTodayKST();
var user = window.TbmState.getUser();
if (user) {
var uid = user.user_id || user.id;
if (uid) {
var worker = window.TbmState.allWorkers.find(function(w) { return String(w.user_id) === String(uid); });
if (worker) {
W.leaderId = worker.user_id;
W.leaderName = worker.worker_name;
} else {
W.leaderId = uid;
W.leaderName = user.name || '';
}
} else {
W.leaderName = user.name || '';
}
}
// 로딩 해제
document.getElementById('loadingOverlay').style.display = 'none';
// 첫 스텝 렌더링
renderStep(1);
updateIndicator();
updateNav();
} catch (error) {
console.error('초기화 오류:', error);
document.getElementById('loadingOverlay').style.display = 'none';
showToast('데이터를 불러오는 중 오류가 발생했습니다.', 'error');
}
});
// waitForApi → api-base.js 전역 사용
// ==================== 네비게이션 ====================
window.nextStep = function() {
console.log('[TBM Create] nextStep called, current step:', W.step, 'workTypeId:', W.workTypeId);
if (!validateStep(W.step)) return;
if (W.step < W.totalSteps) {
W.step++;
renderStep(W.step);
updateIndicator();
updateNav();
window.scrollTo(0, 0);
}
};
window.prevStep = function() {
console.log('[TBM Create] prevStep called, current step:', W.step);
if (W.step > 1) {
W.step--;
renderStep(W.step);
updateIndicator();
updateNav();
window.scrollTo(0, 0);
}
};
window.goBack = function() {
if (W.step > 1) {
window.prevStep();
} else {
window.location.href = '/pages/work/tbm-mobile.html';
}
};
function updateIndicator() {
var steps = document.querySelectorAll('#stepIndicator .step');
var lines = document.querySelectorAll('#stepIndicator .step-line');
steps.forEach(function(el, i) {
el.classList.remove('active', 'completed');
if (i + 1 === W.step) {
el.classList.add('active');
} else if (i + 1 < W.step) {
el.classList.add('completed');
}
});
lines.forEach(function(el, i) {
el.style.background = (i + 1 < W.step) ? '#10b981' : '#e5e7eb';
});
}
// 네비게이션 버튼: 단일 핸들러 (DOM 교체 없이 상태 기반 분기)
var _navAction = { prev: null, next: null };
function updateNav() {
var prevBtn = document.getElementById('prevBtn');
var nextBtn = document.getElementById('nextBtn');
if (W.step === 1) {
prevBtn.style.visibility = 'hidden';
_navAction.prev = null;
} else {
prevBtn.style.visibility = 'visible';
_navAction.prev = window.prevStep;
}
if (W.step === W.totalSteps) {
nextBtn.className = 'nav-btn nav-btn-save';
nextBtn.textContent = '저장';
_navAction.next = saveWizard;
} else {
nextBtn.className = 'nav-btn nav-btn-next';
nextBtn.innerHTML = '다음 →';
_navAction.next = window.nextStep;
}
nextBtn.disabled = false;
}
// 한번만 등록하는 이벤트 리스너
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('prevBtn').addEventListener('click', function(e) {
e.preventDefault();
if (_navAction.prev) _navAction.prev();
});
document.getElementById('nextBtn').addEventListener('click', function(e) {
e.preventDefault();
if (_navAction.next) _navAction.next();
});
});
// ==================== 유효성 검사 ====================
function validateStep(step) {
switch (step) {
case 1: // 작업자 선택
if (W.workers.size === 0) {
showToast('최소 1명의 작업자를 선택해주세요.', 'warning');
return false;
}
return true;
case 2: // 프로젝트 + 공정
if (!W.workTypeId) {
showToast('공정을 선택해주세요.', 'warning');
return false;
}
return true;
default:
return true;
}
}
// ==================== 스텝 렌더링 ====================
function renderStep(step) {
var container = document.getElementById('stepContainer');
switch (step) {
case 1: renderStepWorkers(container); break;
case 2: renderStepProjectAndWorkType(container); break;
case 3: renderStepConfirm(container); break;
}
}
// --- Step 1: 작업자 선택 ---
async function renderStepWorkers(container) {
var workers = window.TbmState.allWorkers;
// 당일 배정 현황 로드 (첫 로드 시)
if (!W.todayAssignments) {
try {
var today = window.TbmUtils.getTodayKST();
var res = await window.apiCall('/tbm/sessions/date/' + today + '/assignments');
if (res && res.success) {
W.todayAssignments = {};
res.data.forEach(function(a) {
if (a.sessions && a.sessions.length > 0) {
W.todayAssignments[a.user_id] = a;
}
});
} else {
W.todayAssignments = {};
}
} catch(e) {
console.error('배정 현황 로드 오류:', e);
W.todayAssignments = {};
}
}
var workerCards = workers.map(function(w) {
var selected = W.workers.has(w.user_id) ? ' selected' : '';
var assignment = W.todayAssignments[w.user_id];
var assigned = assignment && assignment.sessions && assignment.sessions.length > 0;
var badgeHtml = '';
var disabledClass = '';
var onclick = 'toggleWorker(' + w.user_id + ')';
if (assigned) {
// 이미 배정됨 - 선택 불가
var leaderNames = assignment.sessions.map(function(s) { return s.leader_name || ''; }).join(', ');
badgeHtml = '
배정됨 - ' + esc(leaderNames) + ' TBM
';
disabledClass = ' disabled';
onclick = '';
}
return '' +
'
✓
' +
'
' +
'
' + esc(w.worker_name) + '
' +
'
' + esc(w.job_type || '작업자') + '
' +
badgeHtml +
'
' +
'
';
}).join('');
container.innerHTML =
'' +
'
1작업자 선택
' +
'
' +
'' + W.workers.size + '명 선택' +
'' +
'
' +
'
' + workerCards + '
' +
'
';
}
window.toggleWorker = function(workerId) {
// 이미 배정된 작업자는 선택 불가
var a = W.todayAssignments && W.todayAssignments[workerId];
if (a && a.sessions && a.sessions.length > 0) return;
if (W.workers.has(workerId)) {
W.workers.delete(workerId);
delete W.workerNames[workerId];
} else {
W.workers.add(workerId);
var w = window.TbmState.allWorkers.find(function(x) { return x.user_id === workerId; });
if (w) W.workerNames[workerId] = w.worker_name;
}
var card = document.querySelector('[data-wid="' + workerId + '"]');
if (card) card.classList.toggle('selected');
var countEl = document.getElementById('workerCount');
if (countEl) countEl.textContent = W.workers.size + '명 선택';
};
window.toggleAllWorkers = function() {
var workers = window.TbmState.allWorkers;
var availableWorkers = workers.filter(function(w) {
var a = W.todayAssignments && W.todayAssignments[w.user_id];
return !(a && a.sessions && a.sessions.length > 0);
});
if (W.workers.size === availableWorkers.length) {
W.workers.clear();
W.workerNames = {};
} else {
availableWorkers.forEach(function(w) {
W.workers.add(w.user_id);
W.workerNames[w.user_id] = w.worker_name;
});
}
renderStepWorkers(document.getElementById('stepContainer'));
};
// --- Step 2: 프로젝트 + 공정 선택 (통합) ---
function renderStepProjectAndWorkType(container) {
var projects = window.TbmState.allProjects;
var workTypes = window.TbmState.allWorkTypes;
// 프로젝트 선택 UI
var skipSelected = W.projectId === null ? ' selected' : '';
var projectItems = projects.map(function(p) {
var selected = W.projectId === p.project_id ? ' selected' : '';
return '' +
'
' + esc(p.project_name) + '
' +
'
' + esc(p.job_no || '') + '
' +
'
';
}).join('');
// 공정 pill 버튼
var pillHtml = workTypes.map(function(wt) {
var selected = W.workTypeId === wt.id ? ' selected' : '';
return '';
}).join('');
pillHtml += '';
// 공정 인라인 추가 폼
var addWorkTypeFormHtml = '';
if (W.showAddWorkType) {
addWorkTypeFormHtml =
'';
}
container.innerHTML =
'' +
'
2프로젝트 선택 (선택사항)
' +
'
' +
'선택 안함' +
'
' +
(projects.length > 0 ? projectItems : '
등록된 프로젝트가 없습니다
') +
'
' +
'' +
'
2공정 선택 (필수)
' +
'
' + pillHtml + '
' +
addWorkTypeFormHtml +
'
';
// 자동 포커스
if (W.showAddWorkType) {
var inp = document.getElementById('newWorkTypeName');
if (inp) {
setTimeout(function() { inp.focus(); }, 50);
inp.onkeydown = function(e) {
if (e.key === 'Enter') { e.preventDefault(); saveNewWorkType(); }
if (e.key === 'Escape') { cancelAddWorkType(); }
};
}
}
// Event delegation for project/workType selection
container.onclick = function(e) {
var el = e.target.closest('[data-action]');
if (!el) return;
var action = el.getAttribute('data-action');
if (action === 'selectProject') {
var pid = el.getAttribute('data-project-id');
selectProject(pid ? parseInt(pid) : null, el.getAttribute('data-project-name') || '');
} else if (action === 'selectWorkType') {
selectWorkType(parseInt(el.getAttribute('data-wt-id')), el.getAttribute('data-wt-name') || '');
}
};
}
window.selectProject = function(projectId, projectName) {
W.projectId = projectId;
W.projectName = projectName || '';
// Update project list items
document.querySelectorAll('#stepContainer .list-item, #stepContainer .list-item-skip').forEach(function(el) {
el.classList.remove('selected');
});
if (projectId === null) {
var skipEl = document.querySelector('#stepContainer .list-item-skip');
if (skipEl) skipEl.classList.add('selected');
} else {
document.querySelectorAll('#stepContainer .list-item').forEach(function(el) {
var title = el.querySelector('.item-title');
if (title && title.textContent === projectName) {
el.classList.add('selected');
}
});
}
};
window.selectWorkType = function(id, name) {
console.log('[TBM Create] selectWorkType:', id, name);
W.workTypeId = id;
W.workTypeName = name;
// Update pill buttons
document.querySelectorAll('#stepContainer .pill-btn').forEach(function(el) {
el.classList.remove('selected');
});
document.querySelectorAll('#stepContainer .pill-btn').forEach(function(el) {
if (el.textContent === name) {
el.classList.add('selected');
}
});
};
// --- Step 2: 인라인 추가 (공정) ---
window.toggleAddWorkType = function() {
W.showAddWorkType = !W.showAddWorkType;
renderStepProjectAndWorkType(document.getElementById('stepContainer'));
};
window.cancelAddWorkType = function() {
W.showAddWorkType = false;
renderStepProjectAndWorkType(document.getElementById('stepContainer'));
};
window.saveNewWorkType = async function() {
var inp = document.getElementById('newWorkTypeName');
var btn = document.getElementById('btnSaveWorkType');
if (!inp || !btn) return;
var name = inp.value.trim();
if (!name) {
showToast('공정명을 입력해주세요.', 'warning');
inp.focus();
return;
}
var exists = window.TbmState.allWorkTypes.some(function(wt) {
return wt.name.toLowerCase() === name.toLowerCase();
});
if (exists) {
showToast('이미 존재하는 공정명입니다.', 'warning');
inp.focus();
return;
}
btn.disabled = true;
btn.textContent = '저장 중...';
try {
var response = await window.apiCall('/daily-work-reports/work-types', 'POST', { name: name });
if (!response || !response.success) {
throw new Error(response?.message || '공정 추가 실패');
}
var newItem = response.data;
window.TbmState.allWorkTypes.push(newItem);
W.workTypeId = newItem.id;
W.workTypeName = newItem.name;
W.showAddWorkType = false;
renderStepProjectAndWorkType(document.getElementById('stepContainer'));
showToast('\'' + name + '\' 공정이 추가되었습니다.', 'success');
} catch (error) {
console.error('공정 추가 오류:', error);
showToast('공정 추가 중 오류: ' + error.message, 'error');
btn.disabled = false;
btn.textContent = '저장';
}
};
// --- Step 3: 확인 ---
function renderStepConfirm(container) {
var dateDisplay = window.TbmUtils.formatDateFull(W.sessionDate);
// 작업자 이름 목록
var workerNameList = [];
W.workers.forEach(function(wid) {
workerNameList.push(W.workerNames[wid] || '작업자');
});
var summaryHtml =
'' +
'
날짜' + esc(dateDisplay) + '
' +
'
입력자' + esc(W.leaderName || '(미설정)') + '
' +
'
프로젝트' + esc(W.projectName || '선택 안함') + '
' +
'
공정' + esc(W.workTypeName) + '
' +
'
작업자' + W.workers.size + '명
' +
'
';
// 작업자 목록 (간단 표시)
var workerListHtml = workerNameList.map(function(name) {
return '' +
'' + esc(name) + '' +
'세부 미입력' +
'
';
}).join('');
container.innerHTML =
'' +
'
3확인
' +
summaryHtml +
'
' +
'' +
'
작업자 목록
' +
'
' +
'저장 후 TBM 카드를 탭하면 작업자별 작업/작업장을 입력할 수 있습니다.' +
'
' +
workerListHtml +
'
';
}
// ==================== 저장 ====================
var _saving = false;
async function saveWizard() {
if (_saving) return;
_saving = true;
// 로딩 오버레이 표시
var overlay = document.getElementById('loadingOverlay');
var loadingText = document.getElementById('loadingText');
if (overlay) {
if (loadingText) loadingText.textContent = '저장 중...';
overlay.style.display = 'flex';
}
// 저장 버튼 비활성화
var saveBtn = document.getElementById('nextBtn');
if (saveBtn) {
saveBtn.disabled = true;
saveBtn.textContent = '저장 중...';
}
try {
var leaderId = W.leaderId ? parseInt(W.leaderId) : null;
// 1. TBM 세션 생성
var sessionData = {
session_date: W.sessionDate,
leader_user_id: leaderId
};
var response = await window.apiCall('/tbm/sessions', 'POST', sessionData);
if (!response || !response.success) {
throw new Error(response?.message || '세션 생성 실패');
}
var sessionId = response.data.session_id;
// 2. 팀원 일괄 추가 (task_id, workplace_id = null)
var members = [];
W.workers.forEach(function(wid) {
members.push({
user_id: wid,
project_id: W.projectId,
work_type_id: W.workTypeId,
task_id: null,
workplace_category_id: null,
workplace_id: null,
work_detail: null,
is_present: true
});
});
var teamResponse = await window.apiCall(
'/tbm/sessions/' + sessionId + '/team/batch',
'POST',
{ members: members }
);
if (!teamResponse || !teamResponse.success) {
var err = new Error(teamResponse?.message || '팀원 추가 실패');
if (teamResponse && teamResponse.duplicates) err.duplicates = teamResponse.duplicates;
err._sessionId = sessionId;
throw err;
}
showToast('TBM이 생성되었습니다 (작업자 ' + members.length + '명)', 'success');
// 3. tbm-mobile.html로 이동
setTimeout(function() {
window.location.href = '/pages/work/tbm-mobile.html';
}, 1000);
} catch (error) {
console.error('TBM 저장 오류:', error);
// 409 중복 배정 에러 처리
if (error.duplicates && error.duplicates.length > 0) {
// 고아 세션 삭제
if (error._sessionId) {
try { await window.apiCall('/tbm/sessions/' + error._sessionId, 'DELETE'); } catch(e) {}
}
// 중복 작업자 자동 해제
error.duplicates.forEach(function(d) {
W.workers.delete(d.user_id);
delete W.workerNames[d.user_id];
});
// 배정 현황 캐시 갱신
W.todayAssignments = null;
// Step 1로 복귀
W.step = 1;
renderStep(1);
updateIndicator();
updateNav();
showToast(error.message, 'error');
} else {
showToast('TBM 저장 중 오류가 발생했습니다: ' + error.message, 'error');
}
if (overlay) overlay.style.display = 'none';
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.textContent = '저장';
}
_saving = false;
}
}
// ==================== 토스트 (로컬) ====================
function showToast(message, type) {
if (window.showToast && typeof window.showToast === 'function') {
window.showToast(message, type);
return;
}
console.log('[Toast] ' + type + ': ' + message);
}
})();