/** * 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); } })();