feat: 작업장 관리 기능 추가 (공장-작업장 계층 구조)
- 공장(카테고리) 및 작업장 CRUD API 구현 - 탭 기반 UI로 공장별 작업장 필터링 - 터치 최적화된 관리자 페이지 - DB 테이블: workplace_categories, workplaces - 관리자 메뉴에 작업장 관리 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
551
web-ui/js/workplace-management.js
Normal file
551
web-ui/js/workplace-management.js
Normal file
@@ -0,0 +1,551 @@
|
||||
// 작업장 관리 페이지 JavaScript
|
||||
|
||||
// 전역 변수
|
||||
let categories = [];
|
||||
let workplaces = [];
|
||||
let currentCategoryId = '';
|
||||
let currentEditingCategory = null;
|
||||
let currentEditingWorkplace = null;
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🏗️ 작업장 관리 페이지 초기화 시작');
|
||||
|
||||
loadAllData();
|
||||
});
|
||||
|
||||
// 모든 데이터 로드
|
||||
async function loadAllData() {
|
||||
try {
|
||||
await Promise.all([
|
||||
loadCategories(),
|
||||
loadWorkplaces()
|
||||
]);
|
||||
|
||||
renderCategoryTabs();
|
||||
renderWorkplaces();
|
||||
updateStatistics();
|
||||
} catch (error) {
|
||||
console.error('데이터 로딩 오류:', error);
|
||||
showToast('데이터를 불러오는데 실패했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 카테고리(공장) 관련 ====================
|
||||
|
||||
// 카테고리 목록 로드
|
||||
async function loadCategories() {
|
||||
try {
|
||||
const response = await apiCall('/workplaces/categories', 'GET');
|
||||
|
||||
let categoryData = [];
|
||||
if (response && response.success && Array.isArray(response.data)) {
|
||||
categoryData = response.data;
|
||||
} else if (Array.isArray(response)) {
|
||||
categoryData = response;
|
||||
}
|
||||
|
||||
categories = categoryData;
|
||||
console.log(`✅ 카테고리 ${categories.length}개 로드 완료`);
|
||||
} catch (error) {
|
||||
console.error('카테고리 로딩 오류:', error);
|
||||
categories = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 카테고리 탭 렌더링
|
||||
function renderCategoryTabs() {
|
||||
const tabsContainer = document.getElementById('categoryTabs');
|
||||
if (!tabsContainer) return;
|
||||
|
||||
// 전체 탭은 항상 표시
|
||||
let tabsHtml = `
|
||||
<button class="tab-btn ${currentCategoryId === '' ? 'active' : ''}"
|
||||
data-category=""
|
||||
onclick="switchCategory('')">
|
||||
<span class="tab-icon">🏗️</span>
|
||||
전체 (${workplaces.length})
|
||||
</button>
|
||||
`;
|
||||
|
||||
// 각 카테고리 탭 추가
|
||||
categories.forEach(category => {
|
||||
const count = workplaces.filter(w => w.category_id === category.category_id).length;
|
||||
const isActive = currentCategoryId === category.category_id;
|
||||
|
||||
tabsHtml += `
|
||||
<button class="tab-btn ${isActive ? 'active' : ''}"
|
||||
data-category="${category.category_id}"
|
||||
onclick="switchCategory(${category.category_id})">
|
||||
<span class="tab-icon">🏭</span>
|
||||
${category.category_name} (${count})
|
||||
</button>
|
||||
`;
|
||||
});
|
||||
|
||||
tabsContainer.innerHTML = tabsHtml;
|
||||
}
|
||||
|
||||
// 카테고리 전환
|
||||
function switchCategory(categoryId) {
|
||||
currentCategoryId = categoryId === '' ? '' : categoryId;
|
||||
renderCategoryTabs();
|
||||
renderWorkplaces();
|
||||
}
|
||||
|
||||
// 카테고리 모달 열기
|
||||
function openCategoryModal(categoryData = null) {
|
||||
const modal = document.getElementById('categoryModal');
|
||||
const modalTitle = document.getElementById('categoryModalTitle');
|
||||
const deleteBtn = document.getElementById('deleteCategoryBtn');
|
||||
|
||||
if (!modal) return;
|
||||
|
||||
currentEditingCategory = categoryData;
|
||||
|
||||
if (categoryData) {
|
||||
// 수정 모드
|
||||
modalTitle.textContent = '공장 수정';
|
||||
deleteBtn.style.display = 'inline-flex';
|
||||
|
||||
document.getElementById('categoryId').value = categoryData.category_id;
|
||||
document.getElementById('categoryName').value = categoryData.category_name || '';
|
||||
document.getElementById('categoryDescription').value = categoryData.description || '';
|
||||
document.getElementById('categoryOrder').value = categoryData.display_order || 0;
|
||||
} else {
|
||||
// 신규 등록 모드
|
||||
modalTitle.textContent = '공장 추가';
|
||||
deleteBtn.style.display = 'none';
|
||||
|
||||
document.getElementById('categoryForm').reset();
|
||||
document.getElementById('categoryId').value = '';
|
||||
}
|
||||
|
||||
modal.style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('categoryName').focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 카테고리 모달 닫기
|
||||
function closeCategoryModal() {
|
||||
const modal = document.getElementById('categoryModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
currentEditingCategory = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 카테고리 저장
|
||||
async function saveCategory() {
|
||||
try {
|
||||
const categoryId = document.getElementById('categoryId').value;
|
||||
|
||||
const categoryData = {
|
||||
category_name: document.getElementById('categoryName').value.trim(),
|
||||
description: document.getElementById('categoryDescription').value.trim() || null,
|
||||
display_order: parseInt(document.getElementById('categoryOrder').value) || 0,
|
||||
is_active: true
|
||||
};
|
||||
|
||||
if (!categoryData.category_name) {
|
||||
showToast('공장명은 필수 입력 항목입니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('💾 저장할 카테고리 데이터:', categoryData);
|
||||
|
||||
let response;
|
||||
if (categoryId) {
|
||||
// 수정
|
||||
response = await apiCall(`/workplaces/categories/${categoryId}`, 'PUT', categoryData);
|
||||
} else {
|
||||
// 신규 등록
|
||||
response = await apiCall('/workplaces/categories', 'POST', categoryData);
|
||||
}
|
||||
|
||||
if (response && (response.success || response.category_id)) {
|
||||
const action = categoryId ? '수정' : '등록';
|
||||
showToast(`공장이 성공적으로 ${action}되었습니다.`, 'success');
|
||||
|
||||
closeCategoryModal();
|
||||
await loadAllData();
|
||||
} else {
|
||||
throw new Error(response?.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('카테고리 저장 오류:', error);
|
||||
showToast(error.message || '카테고리 저장 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 카테고리 삭제
|
||||
async function deleteCategory() {
|
||||
if (!currentEditingCategory) return;
|
||||
|
||||
if (!confirm(`"${currentEditingCategory.category_name}" 공장을 정말 삭제하시겠습니까?\n\n⚠️ 이 공장에 속한 모든 작업장의 공장 정보가 제거됩니다.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiCall(`/workplaces/categories/${currentEditingCategory.category_id}`, 'DELETE');
|
||||
|
||||
if (response && response.success) {
|
||||
showToast('공장이 성공적으로 삭제되었습니다.', 'success');
|
||||
|
||||
closeCategoryModal();
|
||||
await loadAllData();
|
||||
} else {
|
||||
throw new Error(response?.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('카테고리 삭제 오류:', error);
|
||||
showToast(error.message || '카테고리 삭제 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 작업장 관련 ====================
|
||||
|
||||
// 작업장 목록 로드
|
||||
async function loadWorkplaces() {
|
||||
try {
|
||||
const response = await apiCall('/workplaces', 'GET');
|
||||
|
||||
let workplaceData = [];
|
||||
if (response && response.success && Array.isArray(response.data)) {
|
||||
workplaceData = response.data;
|
||||
} else if (Array.isArray(response)) {
|
||||
workplaceData = response;
|
||||
}
|
||||
|
||||
workplaces = workplaceData;
|
||||
console.log(`✅ 작업장 ${workplaces.length}개 로드 완료`);
|
||||
} catch (error) {
|
||||
console.error('작업장 로딩 오류:', error);
|
||||
workplaces = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 작업장 렌더링
|
||||
function renderWorkplaces() {
|
||||
const grid = document.getElementById('workplaceGrid');
|
||||
if (!grid) return;
|
||||
|
||||
// 현재 카테고리별 필터링
|
||||
const filtered = currentCategoryId === ''
|
||||
? workplaces
|
||||
: workplaces.filter(w => w.category_id == currentCategoryId);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">🏗️</div>
|
||||
<h3>등록된 작업장이 없습니다.</h3>
|
||||
<p>"작업장 추가" 버튼을 눌러 작업장을 등록해보세요.</p>
|
||||
<button class="btn btn-primary" onclick="openWorkplaceModal()">
|
||||
➕ 첫 작업장 추가하기
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
let gridHtml = '';
|
||||
|
||||
filtered.forEach(workplace => {
|
||||
const categoryName = workplace.category_name || '미분류';
|
||||
const isActive = workplace.is_active === 1 || workplace.is_active === true;
|
||||
|
||||
gridHtml += `
|
||||
<div class="code-card workplace-card ${isActive ? '' : 'inactive'}" onclick="editWorkplace(${workplace.workplace_id})">
|
||||
<div class="code-header">
|
||||
<div class="code-icon" style="background: #dbeafe;">🏗️</div>
|
||||
<div class="code-info">
|
||||
<h3 class="code-name">${workplace.workplace_name}</h3>
|
||||
${workplace.category_id ? `<span class="code-label">🏭 ${categoryName}</span>` : ''}
|
||||
</div>
|
||||
<div class="code-actions">
|
||||
<button class="btn-small btn-edit" onclick="event.stopPropagation(); editWorkplace(${workplace.workplace_id})" title="수정">
|
||||
✏️
|
||||
</button>
|
||||
<button class="btn-small btn-delete" onclick="event.stopPropagation(); confirmDeleteWorkplace(${workplace.workplace_id})" title="삭제">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
${workplace.description ? `<p class="code-description">${workplace.description}</p>` : ''}
|
||||
<div class="code-meta">
|
||||
<span class="code-date">등록: ${formatDate(workplace.created_at)}</span>
|
||||
${workplace.updated_at !== workplace.created_at ? `<span class="code-date">수정: ${formatDate(workplace.updated_at)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
grid.innerHTML = gridHtml;
|
||||
}
|
||||
|
||||
// 작업장 모달 열기
|
||||
function openWorkplaceModal(workplaceData = null) {
|
||||
const modal = document.getElementById('workplaceModal');
|
||||
const modalTitle = document.getElementById('workplaceModalTitle');
|
||||
const deleteBtn = document.getElementById('deleteWorkplaceBtn');
|
||||
const categorySelect = document.getElementById('workplaceCategoryId');
|
||||
|
||||
if (!modal) return;
|
||||
|
||||
currentEditingWorkplace = workplaceData;
|
||||
|
||||
// 카테고리 선택 옵션 업데이트
|
||||
let categoryOptions = '<option value="">미분류</option>';
|
||||
categories.forEach(cat => {
|
||||
categoryOptions += `<option value="${cat.category_id}">${cat.category_name}</option>`;
|
||||
});
|
||||
categorySelect.innerHTML = categoryOptions;
|
||||
|
||||
if (workplaceData) {
|
||||
// 수정 모드
|
||||
modalTitle.textContent = '작업장 수정';
|
||||
deleteBtn.style.display = 'inline-flex';
|
||||
|
||||
document.getElementById('workplaceId').value = workplaceData.workplace_id;
|
||||
document.getElementById('workplaceCategoryId').value = workplaceData.category_id || '';
|
||||
document.getElementById('workplaceName').value = workplaceData.workplace_name || '';
|
||||
document.getElementById('workplaceDescription').value = workplaceData.description || '';
|
||||
} else {
|
||||
// 신규 등록 모드
|
||||
modalTitle.textContent = '작업장 추가';
|
||||
deleteBtn.style.display = 'none';
|
||||
|
||||
document.getElementById('workplaceForm').reset();
|
||||
document.getElementById('workplaceId').value = '';
|
||||
|
||||
// 현재 선택된 카테고리가 있으면 자동 선택
|
||||
if (currentCategoryId) {
|
||||
document.getElementById('workplaceCategoryId').value = currentCategoryId;
|
||||
}
|
||||
}
|
||||
|
||||
modal.style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('workplaceName').focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 작업장 모달 닫기
|
||||
function closeWorkplaceModal() {
|
||||
const modal = document.getElementById('workplaceModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
currentEditingWorkplace = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 작업장 편집
|
||||
function editWorkplace(workplaceId) {
|
||||
const workplace = workplaces.find(w => w.workplace_id === workplaceId);
|
||||
if (workplace) {
|
||||
openWorkplaceModal(workplace);
|
||||
} else {
|
||||
showToast('작업장을 찾을 수 없습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 작업장 저장
|
||||
async function saveWorkplace() {
|
||||
try {
|
||||
const workplaceId = document.getElementById('workplaceId').value;
|
||||
|
||||
const workplaceData = {
|
||||
category_id: document.getElementById('workplaceCategoryId').value || null,
|
||||
workplace_name: document.getElementById('workplaceName').value.trim(),
|
||||
description: document.getElementById('workplaceDescription').value.trim() || null,
|
||||
is_active: true
|
||||
};
|
||||
|
||||
if (!workplaceData.workplace_name) {
|
||||
showToast('작업장명은 필수 입력 항목입니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('💾 저장할 작업장 데이터:', workplaceData);
|
||||
|
||||
let response;
|
||||
if (workplaceId) {
|
||||
// 수정
|
||||
response = await apiCall(`/workplaces/${workplaceId}`, 'PUT', workplaceData);
|
||||
} else {
|
||||
// 신규 등록
|
||||
response = await apiCall('/workplaces', 'POST', workplaceData);
|
||||
}
|
||||
|
||||
if (response && (response.success || response.workplace_id)) {
|
||||
const action = workplaceId ? '수정' : '등록';
|
||||
showToast(`작업장이 성공적으로 ${action}되었습니다.`, 'success');
|
||||
|
||||
closeWorkplaceModal();
|
||||
await loadAllData();
|
||||
} else {
|
||||
throw new Error(response?.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('작업장 저장 오류:', error);
|
||||
showToast(error.message || '작업장 저장 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 작업장 삭제 확인
|
||||
function confirmDeleteWorkplace(workplaceId) {
|
||||
const workplace = workplaces.find(w => w.workplace_id === workplaceId);
|
||||
|
||||
if (!workplace) {
|
||||
showToast('작업장을 찾을 수 없습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm(`"${workplace.workplace_name}" 작업장을 정말 삭제하시겠습니까?\n\n⚠️ 삭제된 작업장은 복구할 수 없습니다.`)) {
|
||||
deleteWorkplaceById(workplaceId);
|
||||
}
|
||||
}
|
||||
|
||||
// 작업장 삭제 (수정 모드에서)
|
||||
function deleteWorkplace() {
|
||||
if (currentEditingWorkplace) {
|
||||
confirmDeleteWorkplace(currentEditingWorkplace.workplace_id);
|
||||
}
|
||||
}
|
||||
|
||||
// 작업장 삭제 실행
|
||||
async function deleteWorkplaceById(workplaceId) {
|
||||
try {
|
||||
const response = await apiCall(`/workplaces/${workplaceId}`, 'DELETE');
|
||||
|
||||
if (response && response.success) {
|
||||
showToast('작업장이 성공적으로 삭제되었습니다.', 'success');
|
||||
|
||||
closeWorkplaceModal();
|
||||
await loadAllData();
|
||||
} else {
|
||||
throw new Error(response?.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('작업장 삭제 오류:', error);
|
||||
showToast(error.message || '작업장 삭제 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 유틸리티 ====================
|
||||
|
||||
// 전체 새로고침
|
||||
async function refreshWorkplaces() {
|
||||
const refreshBtn = document.querySelector('.btn-secondary');
|
||||
if (refreshBtn) {
|
||||
const originalText = refreshBtn.innerHTML;
|
||||
refreshBtn.innerHTML = '<span class="btn-icon">⏳</span>새로고침 중...';
|
||||
refreshBtn.disabled = true;
|
||||
|
||||
await loadAllData();
|
||||
|
||||
refreshBtn.innerHTML = originalText;
|
||||
refreshBtn.disabled = false;
|
||||
} else {
|
||||
await loadAllData();
|
||||
}
|
||||
|
||||
showToast('데이터가 새로고침되었습니다.', 'success');
|
||||
}
|
||||
|
||||
// 통계 업데이트
|
||||
function updateStatistics() {
|
||||
const total = workplaces.length;
|
||||
const active = workplaces.filter(w => w.is_active === 1 || w.is_active === true).length;
|
||||
|
||||
document.getElementById('totalCount').textContent = total;
|
||||
document.getElementById('activeCount').textContent = active;
|
||||
}
|
||||
|
||||
// 날짜 포맷팅
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// 토스트 메시지 표시
|
||||
function showToast(message, type = 'info') {
|
||||
// 기존 토스트 제거
|
||||
const existingToast = document.querySelector('.toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
// 새 토스트 생성
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.textContent = message;
|
||||
|
||||
// 스타일 적용
|
||||
Object.assign(toast.style, {
|
||||
position: 'fixed',
|
||||
top: '20px',
|
||||
right: '20px',
|
||||
padding: '12px 24px',
|
||||
borderRadius: '8px',
|
||||
color: 'white',
|
||||
fontWeight: '500',
|
||||
zIndex: '1000',
|
||||
transform: 'translateX(100%)',
|
||||
transition: 'transform 0.3s ease'
|
||||
});
|
||||
|
||||
// 타입별 배경색
|
||||
const colors = {
|
||||
success: '#10b981',
|
||||
error: '#ef4444',
|
||||
warning: '#f59e0b',
|
||||
info: '#3b82f6'
|
||||
};
|
||||
toast.style.backgroundColor = colors[type] || colors.info;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 애니메이션
|
||||
setTimeout(() => {
|
||||
toast.style.transform = 'translateX(0)';
|
||||
}, 100);
|
||||
|
||||
// 자동 제거
|
||||
setTimeout(() => {
|
||||
toast.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.remove();
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 전역 함수로 노출
|
||||
window.switchCategory = switchCategory;
|
||||
window.openCategoryModal = openCategoryModal;
|
||||
window.closeCategoryModal = closeCategoryModal;
|
||||
window.saveCategory = saveCategory;
|
||||
window.deleteCategory = deleteCategory;
|
||||
window.openWorkplaceModal = openWorkplaceModal;
|
||||
window.closeWorkplaceModal = closeWorkplaceModal;
|
||||
window.editWorkplace = editWorkplace;
|
||||
window.saveWorkplace = saveWorkplace;
|
||||
window.deleteWorkplace = deleteWorkplace;
|
||||
window.confirmDeleteWorkplace = confirmDeleteWorkplace;
|
||||
window.refreshWorkplaces = refreshWorkplaces;
|
||||
@@ -35,6 +35,12 @@
|
||||
<span class="menu-text">작업자 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/workplaces.html">
|
||||
<span class="menu-icon">🏗️</span>
|
||||
<span class="menu-text">작업장 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item active">
|
||||
<a href="/pages/admin/codes.html">
|
||||
<span class="menu-icon">🏷️</span>
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
<span class="menu-text">작업자 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/workplaces.html">
|
||||
<span class="menu-icon">🏗️</span>
|
||||
<span class="menu-text">작업장 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/codes.html">
|
||||
<span class="menu-icon">🏷️</span>
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
<span class="menu-text">작업자 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/workplaces.html">
|
||||
<span class="menu-icon">🏗️</span>
|
||||
<span class="menu-text">작업장 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/codes.html">
|
||||
<span class="menu-icon">🏷️</span>
|
||||
|
||||
215
web-ui/pages/admin/workplaces.html
Normal file
215
web-ui/pages/admin/workplaces.html
Normal file
@@ -0,0 +1,215 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>작업장 관리 | (주)테크니컬코리아</title>
|
||||
<link rel="stylesheet" href="/css/design-system.css">
|
||||
<link rel="stylesheet" href="/css/admin-pages.css?v=7">
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js?v=1" defer></script>
|
||||
<script type="module" src="/js/api-config.js?v=3"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 메인 레이아웃: 사이드바 + 콘텐츠 -->
|
||||
<div class="page-container">
|
||||
<!-- 사이드바 -->
|
||||
<aside class="sidebar">
|
||||
<nav class="sidebar-nav">
|
||||
<div class="sidebar-header">
|
||||
<h3 class="sidebar-title">관리 메뉴</h3>
|
||||
</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/projects.html">
|
||||
<span class="menu-icon">📁</span>
|
||||
<span class="menu-text">프로젝트 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/workers.html">
|
||||
<span class="menu-icon">👥</span>
|
||||
<span class="menu-text">작업자 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item active">
|
||||
<a href="/pages/admin/workplaces.html">
|
||||
<span class="menu-icon">🏗️</span>
|
||||
<span class="menu-text">작업장 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/admin/codes.html">
|
||||
<span class="menu-icon">🏷️</span>
|
||||
<span class="menu-text">코드 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-divider"></li>
|
||||
<li class="menu-item">
|
||||
<a href="/pages/dashboard.html">
|
||||
<span class="menu-icon">🏠</span>
|
||||
<span class="menu-text">대시보드로</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="main-content">
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
<span class="title-icon">🏗️</span>
|
||||
작업장 관리
|
||||
</h1>
|
||||
<p class="page-description">공장 및 작업장을 등록하고 관리합니다</p>
|
||||
</div>
|
||||
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" onclick="openCategoryModal()">
|
||||
<span class="btn-icon">➕</span>
|
||||
공장 추가
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="openWorkplaceModal()">
|
||||
<span class="btn-icon">➕</span>
|
||||
작업장 추가
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="refreshWorkplaces()">
|
||||
<span class="btn-icon">🔄</span>
|
||||
새로고침
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 공장(카테고리) 탭 -->
|
||||
<div class="code-tabs" id="categoryTabs">
|
||||
<button class="tab-btn active" data-category="" onclick="switchCategory('')">
|
||||
<span class="tab-icon">🏗️</span>
|
||||
전체
|
||||
</button>
|
||||
<!-- 공장 탭들이 여기에 동적으로 생성됩니다 -->
|
||||
</div>
|
||||
|
||||
<!-- 작업장 목록 -->
|
||||
<div class="code-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">🏭</span>
|
||||
작업장 목록
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="code-stats" id="workplaceStats">
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">🏗️</span>
|
||||
전체 <span id="totalCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">✅</span>
|
||||
활성 <span id="activeCount">0</span>개
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="code-grid" id="workplaceGrid">
|
||||
<!-- 작업장 카드들이 여기에 동적으로 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 공장(카테고리) 추가/수정 모달 -->
|
||||
<div id="categoryModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h2 id="categoryModalTitle">공장 추가</h2>
|
||||
<button class="modal-close-btn" onclick="closeCategoryModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form id="categoryForm" onsubmit="event.preventDefault(); saveCategory();">
|
||||
<input type="hidden" id="categoryId">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">공장명 *</label>
|
||||
<input type="text" id="categoryName" class="form-control" placeholder="예: 제 1공장" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">설명</label>
|
||||
<textarea id="categoryDescription" class="form-control" rows="3" placeholder="공장에 대한 설명을 입력하세요"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">표시 순서</label>
|
||||
<input type="number" id="categoryOrder" class="form-control" value="0" min="0">
|
||||
<small class="form-help">작은 숫자가 먼저 표시됩니다</small>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeCategoryModal()">취소</button>
|
||||
<button type="button" class="btn btn-danger" id="deleteCategoryBtn" onclick="deleteCategory()" style="display: none;">
|
||||
🗑️ 삭제
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveCategory()">
|
||||
💾 저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업장 추가/수정 모달 -->
|
||||
<div id="workplaceModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h2 id="workplaceModalTitle">작업장 추가</h2>
|
||||
<button class="modal-close-btn" onclick="closeWorkplaceModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form id="workplaceForm" onsubmit="event.preventDefault(); saveWorkplace();">
|
||||
<input type="hidden" id="workplaceId">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">소속 공장</label>
|
||||
<select id="workplaceCategoryId" class="form-control">
|
||||
<option value="">공장 선택</option>
|
||||
<!-- 공장 목록이 동적으로 생성됩니다 -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">작업장명 *</label>
|
||||
<input type="text" id="workplaceName" class="form-control" placeholder="예: 서스작업장, 조립구역" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">설명</label>
|
||||
<textarea id="workplaceDescription" class="form-control" rows="4" placeholder="작업장에 대한 설명을 입력하세요"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeWorkplaceModal()">취소</button>
|
||||
<button type="button" class="btn btn-danger" id="deleteWorkplaceBtn" onclick="deleteWorkplace()" style="display: none;">
|
||||
🗑️ 삭제
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveWorkplace()">
|
||||
💾 저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/js/load-navbar.js?v=5"></script>
|
||||
<script type="module" src="/js/workplace-management.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user