feat: TBM 중복 배정 방지, 설비 배치도 좌표계 통일, 구역 상세 CSS 수정

- TBM 팀원 추가 시 중복 배정 검증 및 409 에러 처리 (tbmController, tbmModel, tbm-create.js, tbm.js, tbm/api.js)
- tkuser/tkfb 설비 배치도 좌표계를 좌상단 기준으로 통일 (CSS left/top 방식)
- tkuser 설비 배치도에 드래그 이동, 코너 리사이즈, 배치 버튼 추가
- 대분류 지도 영역 수정 버튼 추가 (workplace-layout-map.js, tkuser-layout-map.js)
- tkfb workplace-status 캔버스 maxWidth 800 통일
- zone-detail.css object-fit:contain 제거 → height:auto로 마커 위치 정확도 개선
- imageUploadService 업로드 경로 Docker 볼륨 마운트 경로로 수정
- repair-management 카테고리 필터 nonconformity → facility 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-05 15:38:11 +09:00
parent 7a12869d26
commit e18983ac06
16 changed files with 465 additions and 72 deletions

View File

@@ -456,21 +456,17 @@ async function renderNewTbmWorkerGrid() {
grid.innerHTML = allWorkers.map(w => {
const checked = selectedWorkersForNewTbm.has(w.user_id) ? 'checked' : '';
const assignment = todayAssignmentsMap[w.user_id];
const fullyAssigned = assignment && assignment.total_hours >= 8;
const partiallyAssigned = assignment && assignment.total_hours > 0 && assignment.total_hours < 8;
const assigned = assignment && assignment.sessions && assignment.sessions.length > 0;
let badgeHtml = '';
let disabledAttr = '';
let disabledStyle = '';
if (fullyAssigned) {
if (assigned) {
const leaderNames = assignment.sessions.map(s => s.leader_name || '').join(', ');
badgeHtml = `<span style="font-size:0.625rem; color:#ef4444; display:block;">${escapeHtml(leaderNames)} TBM (${assignment.total_hours}h)</span>`;
badgeHtml = `<span style="font-size:0.625rem; color:#ef4444; display:block;">배정됨 - ${escapeHtml(leaderNames)} TBM</span>`;
disabledAttr = 'disabled';
disabledStyle = 'opacity:0.5; pointer-events:none;';
} else if (partiallyAssigned) {
const remaining = 8 - assignment.total_hours;
badgeHtml = `<span style="font-size:0.625rem; color:#2563eb; display:block;">${remaining}h 가용</span>`;
}
return `
@@ -493,9 +489,9 @@ function updateNewTbmWorkerCount() {
}
function toggleNewTbmWorker(workerId, checked) {
// 종일 배정된 작업자 선택 방지
// 이미 배정된 작업자 선택 방지
const a = todayAssignmentsMap && todayAssignmentsMap[workerId];
if (a && a.total_hours >= 8) return;
if (a && a.sessions && a.sessions.length > 0) return;
if (checked) {
selectedWorkersForNewTbm.add(workerId);
@@ -512,7 +508,7 @@ window.toggleNewTbmWorker = toggleNewTbmWorker;
function selectAllNewTbmWorkers() {
allWorkers.forEach(w => {
const a = todayAssignmentsMap && todayAssignmentsMap[w.user_id];
if (a && a.total_hours >= 8) return; // 종일 배정 제외
if (a && a.sessions && a.sessions.length > 0) return; // 배정 제외
selectedWorkersForNewTbm.add(w.user_id);
});
document.querySelectorAll('.new-tbm-worker-cb').forEach(cb => {
@@ -743,11 +739,12 @@ async function saveTbmSession() {
});
});
let createdSessionId = null;
try {
const response = await window.TbmAPI.createTbmSession(sessionData);
if (response && response.success) {
const createdSessionId = response.data.session_id;
createdSessionId = response.data.session_id;
console.log('✅ TBM 세션 생성 완료:', createdSessionId);
const teamResponse = await window.TbmAPI.addTeamMembers(createdSessionId, members);
@@ -769,7 +766,25 @@ async function saveTbmSession() {
}
} catch (error) {
console.error('❌ TBM 세션 저장 오류:', error);
showToast('TBM 세션 저장 중 오류가 발생했습니다.', 'error');
// 409 중복 배정 에러 처리
if (error.duplicates && error.duplicates.length > 0) {
// 고아 세션 삭제
if (createdSessionId) {
try { await window.TbmAPI.deleteSession(createdSessionId); } catch(e) {}
}
// 중복 작업자 자동 해제
const dupIds = new Set(error.duplicates.map(d => d.user_id));
dupIds.forEach(uid => {
selectedWorkersForNewTbm.delete(uid);
});
// 배정 현황 캐시 갱신 후 그리드 새로고침
todayAssignmentsMap = null;
await renderNewTbmWorkerGrid();
showToast(error.message, 'error');
} else {
showToast('TBM 세션 저장 중 오류가 발생했습니다.', 'error');
}
}
}
window.saveTbmSession = saveTbmSession;