feat: 작업 관리 페이지에 공정 관리 기능 추가

## 추가 기능
- 공정 추가/수정/삭제 기능 구현
- 공정 관리 모달 UI 추가
- 공정 탭에 편집 버튼 추가 (✏️)

## UI 변경
- 상단에 "공정 추가" 버튼 추가
- 공정 모달: 공정명, 카테고리, 설명 입력 필드
- 각 공정 탭에 편집 아이콘 표시

## JavaScript 함수
- openWorkTypeModal(): 공정 추가 모달 열기
- editWorkType(workTypeId): 공정 수정 모달 열기
- saveWorkType(): 공정 저장 (POST/PUT)
- deleteWorkType(): 공정 삭제 (연결된 작업 확인)
- closeWorkTypeModal(): 모달 닫기

## 검증 로직
- 연결된 작업이 있는 공정은 삭제 불가
- 필수 필드(공정명) 검증

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-01-26 15:18:14 +09:00
parent 6ff5c443be
commit 1fc9dff69f
2 changed files with 176 additions and 1 deletions

View File

@@ -103,9 +103,15 @@ function renderWorkTypeTabs() {
tabsHtml += ` tabsHtml += `
<button class="tab-btn ${isActive ? 'active' : ''}" <button class="tab-btn ${isActive ? 'active' : ''}"
data-work-type="${workType.id}" data-work-type="${workType.id}"
onclick="switchWorkType(${workType.id})"> onclick="switchWorkType(${workType.id})"
style="position: relative; padding-right: 3rem;">
<span class="tab-icon">🔧</span> <span class="tab-icon">🔧</span>
${workType.name} (${count}) ${workType.name} (${count})
<span onclick="event.stopPropagation(); editWorkType(${workType.id});"
style="position: absolute; right: 0.5rem; padding: 0.25rem 0.5rem; opacity: 0.7; cursor: pointer; font-size: 0.75rem;"
title="공정 수정">
✏️
</span>
</button> </button>
`; `;
}); });
@@ -375,3 +381,126 @@ function showToast(message, type = 'info') {
setTimeout(() => toast.remove(), 300); setTimeout(() => toast.remove(), 300);
}, 3000); }, 3000);
} }
// ==================== 공정 관리 ====================
let currentEditingWorkType = null;
// 공정 모달 열기 (신규)
function openWorkTypeModal() {
currentEditingWorkType = null;
document.getElementById('workTypeModalTitle').textContent = '공정 추가';
document.getElementById('workTypeId').value = '';
document.getElementById('workTypeName').value = '';
document.getElementById('workTypeCategory').value = '';
document.getElementById('workTypeDescription').value = '';
document.getElementById('deleteWorkTypeBtn').style.display = 'none';
document.getElementById('workTypeModal').style.display = 'flex';
document.body.style.overflow = 'hidden';
}
window.openWorkTypeModal = openWorkTypeModal;
// 공정 수정 모달 열기
async function editWorkType(workTypeId) {
try {
const workType = workTypes.find(wt => wt.id === workTypeId);
if (!workType) {
showToast('공정 정보를 찾을 수 없습니다.', 'error');
return;
}
currentEditingWorkType = workType;
document.getElementById('workTypeModalTitle').textContent = '공정 수정';
document.getElementById('workTypeId').value = workType.id;
document.getElementById('workTypeName').value = workType.name || '';
document.getElementById('workTypeCategory').value = workType.category || '';
document.getElementById('workTypeDescription').value = workType.description || '';
document.getElementById('deleteWorkTypeBtn').style.display = 'block';
document.getElementById('workTypeModal').style.display = 'flex';
document.body.style.overflow = 'hidden';
} catch (error) {
console.error('❌ 공정 조회 오류:', error);
showToast('공정 정보를 불러올 수 없습니다.', 'error');
}
}
window.editWorkType = editWorkType;
// 공정 모달 닫기
function closeWorkTypeModal() {
document.getElementById('workTypeModal').style.display = 'none';
document.body.style.overflow = 'auto';
currentEditingWorkType = null;
}
window.closeWorkTypeModal = closeWorkTypeModal;
// 공정 저장
async function saveWorkType() {
const workTypeId = document.getElementById('workTypeId').value;
const workTypeData = {
name: document.getElementById('workTypeName').value.trim(),
category: document.getElementById('workTypeCategory').value.trim() || null,
description: document.getElementById('workTypeDescription').value.trim() || null
};
if (!workTypeData.name) {
showToast('공정명을 입력해주세요.', 'error');
return;
}
try {
let response;
if (workTypeId) {
// 수정
response = await window.apiCall(`/daily-work-reports/work-types/${workTypeId}`, 'PUT', workTypeData);
} else {
// 신규
response = await window.apiCall('/daily-work-reports/work-types', 'POST', workTypeData);
}
if (response && response.success) {
showToast(workTypeId ? '공정이 수정되었습니다.' : '공정이 추가되었습니다.', 'success');
closeWorkTypeModal();
await loadAllData();
} else {
throw new Error(response.message || '저장에 실패했습니다.');
}
} catch (error) {
console.error('❌ 공정 저장 오류:', error);
showToast('공정 저장 중 오류가 발생했습니다.', 'error');
}
}
window.saveWorkType = saveWorkType;
// 공정 삭제
async function deleteWorkType() {
if (!currentEditingWorkType) return;
// 이 공정에 속한 작업이 있는지 확인
const relatedTasks = tasks.filter(t => t.work_type_id === currentEditingWorkType.id);
if (relatedTasks.length > 0) {
showToast(`이 공정에 ${relatedTasks.length}개의 작업이 연결되어 있어 삭제할 수 없습니다.`, 'error');
return;
}
if (!confirm(`"${currentEditingWorkType.name}" 공정을 삭제하시겠습니까?`)) {
return;
}
try {
const response = await window.apiCall(`/daily-work-reports/work-types/${currentEditingWorkType.id}`, 'DELETE');
if (response && response.success) {
showToast('공정이 삭제되었습니다.', 'success');
closeWorkTypeModal();
await loadAllData();
} else {
throw new Error(response.message || '삭제에 실패했습니다.');
}
} catch (error) {
console.error('❌ 공정 삭제 오류:', error);
showToast('공정 삭제 중 오류가 발생했습니다.', 'error');
}
}
window.deleteWorkType = deleteWorkType;

View File

@@ -77,6 +77,10 @@
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button class="btn btn-primary" onclick="openWorkTypeModal()">
<span class="btn-icon">🔧</span>
공정 추가
</button>
<button class="btn btn-primary" onclick="openTaskModal()"> <button class="btn btn-primary" onclick="openTaskModal()">
<span class="btn-icon"></span> <span class="btn-icon"></span>
작업 추가 작업 추가
@@ -176,6 +180,48 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 공정 추가/수정 모달 -->
<div id="workTypeModal" class="modal-overlay" style="display: none;">
<div class="modal-container">
<div class="modal-header">
<h2 id="workTypeModalTitle">공정 추가</h2>
<button class="modal-close-btn" onclick="closeWorkTypeModal()">×</button>
</div>
<div class="modal-body">
<form id="workTypeForm" onsubmit="event.preventDefault(); saveWorkType();">
<input type="hidden" id="workTypeId">
<div class="form-group">
<label class="form-label">공정명 *</label>
<input type="text" id="workTypeName" class="form-control" placeholder="예: Base(구조물), Vessel(용기)" required>
</div>
<div class="form-group">
<label class="form-label">카테고리</label>
<input type="text" id="workTypeCategory" class="form-control" placeholder="예: 제작, 조립">
<small class="form-help">공정을 그룹화할 카테고리 (선택사항)</small>
</div>
<div class="form-group">
<label class="form-label">설명</label>
<textarea id="workTypeDescription" class="form-control" rows="3" placeholder="공정에 대한 설명"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeWorkTypeModal()">취소</button>
<button type="button" class="btn btn-danger" id="deleteWorkTypeBtn" onclick="deleteWorkType()" style="display: none;">
🗑️ 삭제
</button>
<button type="button" class="btn btn-primary" onclick="saveWorkType()">
💾 저장
</button>
</div>
</div>
</div>
</div> </div>
<script type="module" src="/js/load-navbar.js?v=5"></script> <script type="module" src="/js/load-navbar.js?v=5"></script>