- 일일순회점검 시스템 신규 구현 - DB 테이블: patrol_checklist_items, daily_patrol_sessions, patrol_check_records, workplace_items, item_types - API: /api/patrol/* 엔드포인트 - 프론트엔드: 지도 기반 작업장 점검 UI - 설비 관리 기능 개선 - 구매 관련 필드 추가 (구매일, 가격, 공급업체 등) - 설비 코드 자동 생성 (TKP-XXX 형식) - 작업장 관리 개선 - 레이아웃 이미지 업로드 기능 - 마커 위치 저장 기능 - 부서 관리 기능 추가 - 사이드바 네비게이션 카테고리 재구성 - 이미지 401 오류 수정 (정적 파일 경로 처리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
340 lines
11 KiB
JavaScript
340 lines
11 KiB
JavaScript
// department-management.js
|
|
// 부서 관리 페이지 JavaScript
|
|
|
|
let departments = [];
|
|
let selectedDepartmentId = null;
|
|
let selectedWorkers = new Set();
|
|
|
|
// 페이지 초기화
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await waitForApiConfig();
|
|
await loadDepartments();
|
|
});
|
|
|
|
// API 설정 로드 대기
|
|
async function waitForApiConfig() {
|
|
let retryCount = 0;
|
|
while (!window.apiCall && retryCount < 50) {
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
retryCount++;
|
|
}
|
|
if (!window.apiCall) {
|
|
console.error('API 설정 로드 실패');
|
|
}
|
|
}
|
|
|
|
// 부서 목록 로드
|
|
async function loadDepartments() {
|
|
try {
|
|
const result = await window.apiCall('/departments');
|
|
|
|
if (result.success) {
|
|
departments = result.data;
|
|
renderDepartmentList();
|
|
updateMoveToDepartmentSelect();
|
|
}
|
|
} catch (error) {
|
|
console.error('부서 목록 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
// 부서 목록 렌더링
|
|
function renderDepartmentList() {
|
|
const container = document.getElementById('departmentList');
|
|
|
|
if (departments.length === 0) {
|
|
container.innerHTML = `
|
|
<div style="text-align: center; padding: 2rem; color: #9ca3af;">
|
|
등록된 부서가 없습니다.<br>
|
|
<button class="btn btn-primary btn-sm" style="margin-top: 1rem;" onclick="openDepartmentModal()">
|
|
첫 부서 등록하기
|
|
</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = departments.map(dept => `
|
|
<div class="department-item ${selectedDepartmentId === dept.department_id ? 'active' : ''}"
|
|
onclick="selectDepartment(${dept.department_id})">
|
|
<div class="department-info">
|
|
<span class="department-name">${dept.department_name}</span>
|
|
<span class="department-count">${dept.worker_count || 0}명</span>
|
|
</div>
|
|
<div class="department-actions" onclick="event.stopPropagation()">
|
|
<button class="btn-icon" onclick="editDepartment(${dept.department_id})" title="수정">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
</svg>
|
|
</button>
|
|
<button class="btn-icon danger" onclick="deleteDepartment(${dept.department_id})" title="삭제">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="3 6 5 6 21 6"/>
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// 부서 선택
|
|
async function selectDepartment(departmentId) {
|
|
selectedDepartmentId = departmentId;
|
|
selectedWorkers.clear();
|
|
updateBulkActions();
|
|
renderDepartmentList();
|
|
|
|
const dept = departments.find(d => d.department_id === departmentId);
|
|
document.getElementById('workerListTitle').textContent = `${dept.department_name} 작업자`;
|
|
document.getElementById('addWorkerBtn').style.display = 'inline-flex';
|
|
|
|
await loadWorkers(departmentId);
|
|
}
|
|
|
|
// 부서별 작업자 로드
|
|
async function loadWorkers(departmentId) {
|
|
try {
|
|
const result = await window.apiCall(`/departments/${departmentId}/workers`);
|
|
|
|
if (result.success) {
|
|
renderWorkerList(result.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('작업자 목록 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
// 작업자 목록 렌더링
|
|
function renderWorkerList(workers) {
|
|
const container = document.getElementById('workerList');
|
|
|
|
if (workers.length === 0) {
|
|
container.innerHTML = `
|
|
<div style="text-align: center; padding: 2rem; color: #9ca3af;">
|
|
이 부서에 소속된 작업자가 없습니다.
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = workers.map(worker => `
|
|
<div class="worker-card ${selectedWorkers.has(worker.worker_id) ? 'selected' : ''}"
|
|
onclick="toggleWorkerSelection(${worker.worker_id})">
|
|
<div class="worker-info-row">
|
|
<input type="checkbox" ${selectedWorkers.has(worker.worker_id) ? 'checked' : ''}
|
|
onclick="event.stopPropagation(); toggleWorkerSelection(${worker.worker_id})">
|
|
<div class="worker-avatar">${worker.worker_name.charAt(0)}</div>
|
|
<div class="worker-details">
|
|
<span class="worker-name">${worker.worker_name}</span>
|
|
<span class="worker-job">${getJobTypeName(worker.job_type)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// 직책 한글 변환
|
|
function getJobTypeName(jobType) {
|
|
const names = {
|
|
leader: '그룹장',
|
|
worker: '작업자',
|
|
admin: '관리자'
|
|
};
|
|
return names[jobType] || jobType || '-';
|
|
}
|
|
|
|
// 작업자 선택 토글
|
|
function toggleWorkerSelection(workerId) {
|
|
if (selectedWorkers.has(workerId)) {
|
|
selectedWorkers.delete(workerId);
|
|
} else {
|
|
selectedWorkers.add(workerId);
|
|
}
|
|
updateBulkActions();
|
|
|
|
// 선택 상태 업데이트
|
|
const card = document.querySelector(`.worker-card[onclick*="${workerId}"]`);
|
|
if (card) {
|
|
card.classList.toggle('selected', selectedWorkers.has(workerId));
|
|
const checkbox = card.querySelector('input[type="checkbox"]');
|
|
if (checkbox) checkbox.checked = selectedWorkers.has(workerId);
|
|
}
|
|
}
|
|
|
|
// 일괄 작업 영역 업데이트
|
|
function updateBulkActions() {
|
|
const bulkActions = document.getElementById('bulkActions');
|
|
const selectedCount = document.getElementById('selectedCount');
|
|
|
|
if (selectedWorkers.size > 0) {
|
|
bulkActions.classList.add('visible');
|
|
selectedCount.textContent = selectedWorkers.size;
|
|
} else {
|
|
bulkActions.classList.remove('visible');
|
|
}
|
|
}
|
|
|
|
// 이동 대상 부서 선택 업데이트
|
|
function updateMoveToDepartmentSelect() {
|
|
const select = document.getElementById('moveToDepartment');
|
|
select.innerHTML = '<option value="">부서 이동...</option>' +
|
|
departments.map(d => `<option value="${d.department_id}">${d.department_name}</option>`).join('');
|
|
}
|
|
|
|
// 선택한 작업자 이동
|
|
async function moveSelectedWorkers() {
|
|
const targetDepartmentId = document.getElementById('moveToDepartment').value;
|
|
|
|
if (!targetDepartmentId) {
|
|
alert('이동할 부서를 선택하세요.');
|
|
return;
|
|
}
|
|
|
|
if (selectedWorkers.size === 0) {
|
|
alert('이동할 작업자를 선택하세요.');
|
|
return;
|
|
}
|
|
|
|
if (parseInt(targetDepartmentId) === selectedDepartmentId) {
|
|
alert('같은 부서로는 이동할 수 없습니다.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const result = await window.apiCall('/departments/move-workers', 'POST', {
|
|
workerIds: Array.from(selectedWorkers),
|
|
departmentId: parseInt(targetDepartmentId)
|
|
});
|
|
|
|
if (result.success) {
|
|
alert(result.message);
|
|
selectedWorkers.clear();
|
|
updateBulkActions();
|
|
document.getElementById('moveToDepartment').value = '';
|
|
await loadDepartments();
|
|
await loadWorkers(selectedDepartmentId);
|
|
} else {
|
|
alert(result.error || '이동 실패');
|
|
}
|
|
} catch (error) {
|
|
console.error('작업자 이동 실패:', error);
|
|
alert('작업자 이동에 실패했습니다.');
|
|
}
|
|
}
|
|
|
|
// 부서 모달 열기
|
|
function openDepartmentModal(departmentId = null) {
|
|
const modal = document.getElementById('departmentModal');
|
|
const title = document.getElementById('departmentModalTitle');
|
|
const form = document.getElementById('departmentForm');
|
|
|
|
// 상위 부서 선택 옵션 업데이트
|
|
const parentSelect = document.getElementById('parentDepartment');
|
|
parentSelect.innerHTML = '<option value="">없음 (최상위 부서)</option>' +
|
|
departments
|
|
.filter(d => d.department_id !== departmentId)
|
|
.map(d => `<option value="${d.department_id}">${d.department_name}</option>`)
|
|
.join('');
|
|
|
|
if (departmentId) {
|
|
const dept = departments.find(d => d.department_id === departmentId);
|
|
title.textContent = '부서 수정';
|
|
document.getElementById('departmentId').value = dept.department_id;
|
|
document.getElementById('departmentName').value = dept.department_name;
|
|
document.getElementById('parentDepartment').value = dept.parent_id || '';
|
|
document.getElementById('departmentDescription').value = dept.description || '';
|
|
document.getElementById('displayOrder').value = dept.display_order || 0;
|
|
document.getElementById('isActive').checked = dept.is_active;
|
|
} else {
|
|
title.textContent = '새 부서 등록';
|
|
form.reset();
|
|
document.getElementById('departmentId').value = '';
|
|
document.getElementById('isActive').checked = true;
|
|
}
|
|
|
|
modal.classList.add('show');
|
|
}
|
|
|
|
// 부서 모달 닫기
|
|
function closeDepartmentModal() {
|
|
document.getElementById('departmentModal').classList.remove('show');
|
|
}
|
|
|
|
// 부서 저장
|
|
async function saveDepartment(event) {
|
|
event.preventDefault();
|
|
|
|
const departmentId = document.getElementById('departmentId').value;
|
|
const data = {
|
|
department_name: document.getElementById('departmentName').value,
|
|
parent_id: document.getElementById('parentDepartment').value || null,
|
|
description: document.getElementById('departmentDescription').value,
|
|
display_order: parseInt(document.getElementById('displayOrder').value) || 0,
|
|
is_active: document.getElementById('isActive').checked
|
|
};
|
|
|
|
try {
|
|
const url = departmentId ? `/departments/${departmentId}` : '/departments';
|
|
const method = departmentId ? 'PUT' : 'POST';
|
|
|
|
const result = await window.apiCall(url, method, data);
|
|
|
|
if (result.success) {
|
|
alert(result.message);
|
|
closeDepartmentModal();
|
|
await loadDepartments();
|
|
} else {
|
|
alert(result.error || '저장 실패');
|
|
}
|
|
} catch (error) {
|
|
console.error('부서 저장 실패:', error);
|
|
alert('부서 저장에 실패했습니다.');
|
|
}
|
|
}
|
|
|
|
// 부서 수정
|
|
function editDepartment(departmentId) {
|
|
openDepartmentModal(departmentId);
|
|
}
|
|
|
|
// 부서 삭제
|
|
async function deleteDepartment(departmentId) {
|
|
const dept = departments.find(d => d.department_id === departmentId);
|
|
|
|
if (!confirm(`"${dept.department_name}" 부서를 삭제하시겠습니까?\n\n소속 작업자가 있거나 하위 부서가 있으면 삭제할 수 없습니다.`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const result = await window.apiCall(`/departments/${departmentId}`, 'DELETE');
|
|
|
|
if (result.success) {
|
|
alert('부서가 삭제되었습니다.');
|
|
if (selectedDepartmentId === departmentId) {
|
|
selectedDepartmentId = null;
|
|
document.getElementById('workerListTitle').textContent = '부서를 선택하세요';
|
|
document.getElementById('addWorkerBtn').style.display = 'none';
|
|
document.getElementById('workerList').innerHTML = `
|
|
<div style="text-align: center; padding: 2rem; color: #9ca3af;">
|
|
왼쪽에서 부서를 선택하면 해당 부서의 작업자가 표시됩니다.
|
|
</div>
|
|
`;
|
|
}
|
|
await loadDepartments();
|
|
} else {
|
|
alert(result.error || '삭제 실패');
|
|
}
|
|
} catch (error) {
|
|
console.error('부서 삭제 실패:', error);
|
|
alert('부서 삭제에 실패했습니다.');
|
|
}
|
|
}
|
|
|
|
// 작업자 추가 모달 (작업자 관리 페이지로 이동)
|
|
function openAddWorkerModal() {
|
|
alert('작업자 관리 페이지에서 작업자를 등록한 후 이 페이지에서 부서를 배정하세요.');
|
|
// window.location.href = '/pages/admin/workers.html';
|
|
}
|