feat: tkuser 통합 관리 서비스 + 전체 시스템 SSO 쿠키 인증 통합
- tkuser 서비스 신규 추가 (API + Web) - 사용자/권한/프로젝트/부서/작업자/작업장/설비/작업/휴가 통합 관리 - 작업장 탭: 공장→작업장 드릴다운 네비게이션 + 구역지도 클릭 연동 - 작업 탭: 공정(work_types)→작업(tasks) 계층 관리 - 휴가 탭: 유형 관리 + 연차 배정(근로기준법 자동계산) - 전 시스템 SSO 쿠키 인증으로 통합 (.technicalkorea.net 공유) - System 2: 작업 이슈 리포트 기능 강화 - System 3: tkuser API 연동, 페이지 권한 체계 적용 - docker-compose에 tkuser-api, tkuser-web 서비스 추가 - ARCHITECTURE.md, DEPLOYMENT.md 문서 작성 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,16 +3,17 @@
|
||||
*/
|
||||
|
||||
// API 설정
|
||||
const API_BASE = window.API_BASE_URL || 'http://localhost:20005/api';
|
||||
const API_BASE = window.API_BASE_URL || 'http://localhost:30005/api';
|
||||
|
||||
// 상태 변수
|
||||
let selectedFactoryId = null;
|
||||
let selectedWorkplaceId = null;
|
||||
let selectedWorkplaceName = null;
|
||||
let selectedType = null; // 'nonconformity' | 'safety'
|
||||
let selectedType = null; // 'nonconformity' | 'safety' | 'facility'
|
||||
let selectedCategoryId = null;
|
||||
let selectedCategoryName = null;
|
||||
let selectedItemId = null;
|
||||
let customItemName = null;
|
||||
let selectedTbmSessionId = null;
|
||||
let selectedVisitRequestId = null;
|
||||
let photos = [null, null, null, null, null];
|
||||
@@ -166,10 +167,9 @@ async function loadMapImage() {
|
||||
if (data.success && data.data) {
|
||||
const selectedCategory = data.data.find(c => c.category_id == selectedFactoryId);
|
||||
if (selectedCategory && selectedCategory.layout_image) {
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', '');
|
||||
const fullImageUrl = selectedCategory.layout_image.startsWith('http')
|
||||
? selectedCategory.layout_image
|
||||
: `${baseUrl}${selectedCategory.layout_image}`;
|
||||
: selectedCategory.layout_image;
|
||||
|
||||
canvasImage = new Image();
|
||||
canvasImage.onload = () => renderMap();
|
||||
@@ -251,7 +251,7 @@ function renderMap() {
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 배치도 이미지
|
||||
if (canvasImage && canvasImage.complete) {
|
||||
if (canvasImage && canvasImage.complete && canvasImage.naturalWidth > 0) {
|
||||
const scale = Math.min(canvas.width / canvasImage.width, canvas.height / canvasImage.height);
|
||||
const x = (canvas.width - canvasImage.width * scale) / 2;
|
||||
const y = (canvas.height - canvasImage.height * scale) / 2;
|
||||
@@ -587,6 +587,14 @@ function renderItems(items) {
|
||||
btn.onclick = () => onItemSelect(item, btn);
|
||||
grid.appendChild(btn);
|
||||
});
|
||||
|
||||
// 직접 입력 버튼 추가
|
||||
const customBtn = document.createElement('button');
|
||||
customBtn.type = 'button';
|
||||
customBtn.className = 'item-btn custom-input-btn';
|
||||
customBtn.textContent = '+ 직접 입력';
|
||||
customBtn.onclick = () => showCustomItemInput(customBtn);
|
||||
grid.appendChild(customBtn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -598,6 +606,21 @@ function onItemSelect(item, btn) {
|
||||
btn.classList.add('selected');
|
||||
|
||||
selectedItemId = item.item_id;
|
||||
customItemName = null;
|
||||
|
||||
// 직접 입력 영역 숨기기
|
||||
const customInput = document.getElementById('customItemInput');
|
||||
if (customInput) {
|
||||
customInput.style.display = 'none';
|
||||
document.getElementById('customItemName').value = '';
|
||||
}
|
||||
|
||||
// 직접 입력 버튼 텍스트 초기화
|
||||
const customBtn = document.querySelector('.item-btn.custom-input-btn');
|
||||
if (customBtn) {
|
||||
customBtn.textContent = '+ 직접 입력';
|
||||
}
|
||||
|
||||
updateStepStatus();
|
||||
}
|
||||
|
||||
@@ -667,9 +690,9 @@ function updateStepStatus() {
|
||||
steps[2].classList.toggle('active', step2Complete);
|
||||
|
||||
// Step 3: 항목
|
||||
const step3Complete = selectedItemId;
|
||||
steps[2].classList.toggle('completed', step3Complete);
|
||||
steps[3].classList.toggle('active', step3Complete);
|
||||
const step3Complete = selectedItemId || (selectedItemId === 'custom' && customItemName);
|
||||
steps[2].classList.toggle('completed', !!step3Complete);
|
||||
steps[3].classList.toggle('active', !!step3Complete);
|
||||
|
||||
// 제출 버튼 활성화
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
@@ -697,7 +720,8 @@ async function submitReport() {
|
||||
tbm_session_id: selectedTbmSessionId,
|
||||
visit_request_id: selectedVisitRequestId,
|
||||
issue_category_id: selectedCategoryId,
|
||||
issue_item_id: selectedItemId,
|
||||
issue_item_id: selectedItemId === 'custom' ? null : selectedItemId,
|
||||
custom_item_name: customItemName || null,
|
||||
additional_description: additionalDescription || null,
|
||||
photos: photos.filter(p => p !== null)
|
||||
};
|
||||
@@ -728,6 +752,77 @@ async function submitReport() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 직접 입력 버튼 클릭
|
||||
*/
|
||||
function showCustomItemInput(btn) {
|
||||
// 기존 항목 선택 해제
|
||||
document.querySelectorAll('.item-btn').forEach(b => b.classList.remove('selected'));
|
||||
btn.classList.add('selected');
|
||||
selectedItemId = null;
|
||||
customItemName = null;
|
||||
|
||||
const customInput = document.getElementById('customItemInput');
|
||||
if (customInput) {
|
||||
customInput.style.display = 'flex';
|
||||
document.getElementById('customItemName').focus();
|
||||
}
|
||||
updateStepStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 직접 입력 확인
|
||||
*/
|
||||
function confirmCustomItem() {
|
||||
const input = document.getElementById('customItemName');
|
||||
const name = input.value.trim();
|
||||
if (!name) {
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
customItemName = name;
|
||||
selectedItemId = 'custom';
|
||||
updateStepStatus();
|
||||
|
||||
// 직접 입력 UI 숨기되 값은 유지
|
||||
const customInput = document.getElementById('customItemInput');
|
||||
if (customInput) {
|
||||
customInput.style.display = 'none';
|
||||
}
|
||||
|
||||
// 직접 입력 버튼 텍스트 업데이트
|
||||
const customBtn = document.querySelector('.item-btn.custom-input-btn');
|
||||
if (customBtn) {
|
||||
customBtn.textContent = `✓ ${name}`;
|
||||
customBtn.classList.add('selected');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 직접 입력 취소
|
||||
*/
|
||||
function cancelCustomItem() {
|
||||
const customInput = document.getElementById('customItemInput');
|
||||
if (customInput) {
|
||||
customInput.style.display = 'none';
|
||||
document.getElementById('customItemName').value = '';
|
||||
}
|
||||
|
||||
customItemName = null;
|
||||
if (selectedItemId === 'custom') {
|
||||
selectedItemId = null;
|
||||
}
|
||||
|
||||
// 직접 입력 버튼 상태 초기화
|
||||
const customBtn = document.querySelector('.item-btn.custom-input-btn');
|
||||
if (customBtn) {
|
||||
customBtn.textContent = '+ 직접 입력';
|
||||
customBtn.classList.remove('selected');
|
||||
}
|
||||
updateStepStatus();
|
||||
}
|
||||
|
||||
// 기타 위치 입력 시 위치 정보 업데이트
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const customLocationInput = document.getElementById('customLocation');
|
||||
|
||||
Reference in New Issue
Block a user