feat: 모바일 UX 대폭 개선 + PWA 구현 + 로그인 루프 수정
- 모바일 하단 네비: 메뉴 제거, 4개 핵심 기능(홈/TBM/작업보고/출근) SVG 아이콘 - 모바일 사이드바 스킵: 768px 이하에서 사이드바 미로드, 레이아웃 오프셋 해결 - 모바일 헤더: 햄버거 메뉴 숨김, 본문 margin/overflow 정리 - TBM 모바일: 풀스크린 모달, 저장 버튼 하단 고정, 터치 UX 개선 - PWA: manifest.json, sw.js(network-first), 앱 아이콘, iOS 메타태그, 킬스위치 - 로그인 무한루프 수정: 토큰 만료 검증, 쿠키 정리, loginPage 경로 수정 - 신고 메뉴 tkreport 리다이렉트: navbar + sidebar cross-system-link 적용 - TBM API: 작업장별 안전점검 체크리스트 조회 엔드포인트 추가 - 안전점검 체크리스트 관리 UI 개선 - tkuser: 이슈유형 관리 기능 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<link rel="preload" href="https://cdn.tailwindcss.com" as="script">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="/static/css/tkuser.css?v=20260223">
|
||||
<link rel="stylesheet" href="/static/css/tkuser.css?v=20260224">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
@@ -43,6 +43,9 @@
|
||||
<button class="tab-btn px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap" onclick="switchTab('workplaces')">
|
||||
<i class="fas fa-building mr-2"></i>작업장
|
||||
</button>
|
||||
<button class="tab-btn px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap" onclick="switchTab('workers')">
|
||||
<i class="fas fa-hard-hat mr-2"></i>작업자
|
||||
</button>
|
||||
<button class="tab-btn px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap" onclick="switchTab('departments')">
|
||||
<i class="fas fa-sitemap mr-2"></i>부서
|
||||
</button>
|
||||
@@ -315,6 +318,56 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- ============ 작업자 탭 ============ -->
|
||||
<div id="tab-workers" class="hidden">
|
||||
<div class="grid lg:grid-cols-5 gap-6">
|
||||
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm p-5">
|
||||
<h2 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-hard-hat text-slate-400 mr-2"></i>작업자 등록</h2>
|
||||
<form id="addWorkerForm" class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">이름 <span class="text-red-400">*</span></label>
|
||||
<input type="text" id="newWorkerName" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" placeholder="작업자 이름" required>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">직종</label>
|
||||
<select id="newJobType" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
<option value="">선택</option>
|
||||
<option value="leader">반장</option>
|
||||
<option value="worker">작업자</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">부서</label>
|
||||
<select id="newWorkerDept" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
<option value="">선택</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">연락처</label>
|
||||
<input type="text" id="newWorkerPhone" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" placeholder="010-0000-0000">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">입사일</label>
|
||||
<input type="date" id="newWorkerHireDate" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">메모</label>
|
||||
<input type="text" id="newWorkerNotes" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" placeholder="비고">
|
||||
</div>
|
||||
<button type="submit" class="w-full px-4 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-800 text-sm font-medium">
|
||||
<i class="fas fa-plus mr-1"></i>추가
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="lg:col-span-3 bg-white rounded-xl shadow-sm p-5">
|
||||
<h2 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-people-group text-slate-400 mr-2"></i>작업자 목록</h2>
|
||||
<div id="workerList" class="space-y-2 max-h-[520px] overflow-y-auto">
|
||||
<div class="text-gray-400 text-center py-8"><i class="fas fa-spinner fa-spin text-2xl"></i><p class="mt-2 text-sm">로딩 중...</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ============ 부서 탭 ============ -->
|
||||
<div id="tab-departments" class="hidden">
|
||||
<div class="grid lg:grid-cols-5 gap-6">
|
||||
@@ -869,6 +922,71 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업자 편집 모달 -->
|
||||
<div id="editWorkerModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-xl max-w-md w-full p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-base font-semibold text-gray-900">작업자 정보 수정</h3>
|
||||
<button onclick="closeWorkerModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<form id="editWorkerForm" class="space-y-3">
|
||||
<input type="hidden" id="editWorkerId">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">이름</label>
|
||||
<input type="text" id="editWorkerName" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" required>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">직종</label>
|
||||
<select id="editJobType" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
<option value="">선택</option>
|
||||
<option value="leader">반장</option>
|
||||
<option value="worker">작업자</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">부서</label>
|
||||
<select id="editWorkerDept" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
<option value="">선택</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">연락처</label>
|
||||
<input type="text" id="editWorkerPhone" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">입사일</label>
|
||||
<input type="date" id="editWorkerHireDate" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">메모</label>
|
||||
<input type="text" id="editWorkerNotes" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">상태</label>
|
||||
<select id="editWorkerStatus" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
<option value="active">재직</option>
|
||||
<option value="inactive">비활성</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">재직 상태</label>
|
||||
<select id="editEmploymentStatus" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
<option value="employed">재직</option>
|
||||
<option value="resigned">퇴직</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3 pt-3">
|
||||
<button type="button" onclick="closeWorkerModal()" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 text-sm">취소</button>
|
||||
<button type="submit" class="flex-1 px-4 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-800 text-sm font-medium"><i class="fas fa-save mr-1"></i>저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 부서 편집 모달 -->
|
||||
<div id="editDepartmentModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-xl max-w-md w-full p-6">
|
||||
@@ -1270,18 +1388,18 @@
|
||||
</div>
|
||||
|
||||
<!-- JS: Core (config, token, api, toast, helpers, init) -->
|
||||
<script src="/static/js/tkuser-core.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-core.js?v=20260224"></script>
|
||||
<!-- JS: Tabs -->
|
||||
<script src="/static/js/tkuser-tabs.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-tabs.js?v=20260224"></script>
|
||||
<!-- JS: Individual modules -->
|
||||
<script src="/static/js/tkuser-users.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-projects.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-departments.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-issue-types.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-workplaces.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-tasks.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-vacations.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-layout-map.js?v=20260223"></script>
|
||||
<script src="/static/js/tkuser-users.js?v=20260224"></script>
|
||||
<script src="/static/js/tkuser-projects.js?v=20260224"></script>
|
||||
<script src="/static/js/tkuser-departments.js?v=20260224"></script>
|
||||
<script src="/static/js/tkuser-issue-types.js?v=20260224"></script>
|
||||
<script src="/static/js/tkuser-workplaces.js?v=20260224"></script>
|
||||
<script src="/static/js/tkuser-tasks.js?v=20260224"></script>
|
||||
<script src="/static/js/tkuser-vacations.js?v=20260224"></script>
|
||||
<script src="/static/js/tkuser-layout-map.js?v=20260224"></script>
|
||||
<!-- Boot -->
|
||||
<script>init();</script>
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,208 @@
|
||||
/* ===== Issue Types ===== */
|
||||
/* Placeholder module for issue type CRUD operations.
|
||||
This file is reserved for future issue category management functionality.
|
||||
Currently, issue types are managed through System 3 permissions in tkuser-users.js.
|
||||
*/
|
||||
/* ===== Issue Types CRUD ===== */
|
||||
let issueCategories = [], issueItems = [], issueTypesLoaded = false;
|
||||
let currentIssueType = 'nonconformity';
|
||||
|
||||
const SEVERITY_LABEL = { low: '낮음', medium: '보통', high: '높음', critical: '심각' };
|
||||
const SEVERITY_CLASS = { low: 'bg-gray-50 text-gray-500', medium: 'bg-blue-50 text-blue-600', high: 'bg-amber-50 text-amber-600', critical: 'bg-red-50 text-red-600' };
|
||||
const TYPE_LABEL = { nonconformity: '부적합', safety: '안전', facility: '시설설비' };
|
||||
|
||||
async function loadIssueTypes() {
|
||||
try {
|
||||
const [catRes, itemRes] = await Promise.all([
|
||||
api('/work-issues/categories'),
|
||||
api('/work-issues/items')
|
||||
]);
|
||||
issueCategories = catRes.data || catRes;
|
||||
issueItems = itemRes.data || itemRes;
|
||||
issueTypesLoaded = true;
|
||||
populateIssueCategorySelects();
|
||||
displayIssueCategories();
|
||||
} catch (err) {
|
||||
document.getElementById('issueCategoryList').innerHTML = `<div class="text-red-500 text-center py-6"><i class="fas fa-exclamation-triangle text-xl"></i><p class="text-sm mt-2">${err.message}</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function populateIssueCategorySelects() {
|
||||
['newIssueItemCategory', 'editIssueItemCategory'].forEach(id => {
|
||||
const sel = document.getElementById(id); if (!sel) return;
|
||||
const val = sel.value;
|
||||
sel.innerHTML = '<option value="">선택</option>';
|
||||
issueCategories
|
||||
.filter(c => c.is_active !== 0 && c.is_active !== false)
|
||||
.sort((a, b) => (a.display_order || 0) - (b.display_order || 0))
|
||||
.forEach(c => {
|
||||
const o = document.createElement('option');
|
||||
o.value = c.category_id;
|
||||
o.textContent = `[${TYPE_LABEL[c.category_type] || c.category_type}] ${c.category_name}`;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
sel.value = val;
|
||||
});
|
||||
}
|
||||
|
||||
function switchIssueType(type) {
|
||||
currentIssueType = type;
|
||||
['nonconformity', 'safety', 'facility'].forEach(t => {
|
||||
const btn = document.getElementById('issueTypeToggle' + t.charAt(0).toUpperCase() + t.slice(1));
|
||||
if (!btn) return;
|
||||
if (t === type) {
|
||||
btn.className = 'px-3 py-1 rounded-md text-xs font-medium bg-slate-700 text-white';
|
||||
} else {
|
||||
btn.className = 'px-3 py-1 rounded-md text-xs font-medium text-gray-500 hover:bg-gray-200';
|
||||
}
|
||||
});
|
||||
displayIssueCategories();
|
||||
}
|
||||
|
||||
function displayIssueCategories() {
|
||||
const c = document.getElementById('issueCategoryList');
|
||||
const filtered = issueCategories
|
||||
.filter(cat => cat.category_type === currentIssueType)
|
||||
.sort((a, b) => (a.display_order || 0) - (b.display_order || 0));
|
||||
|
||||
if (!filtered.length) {
|
||||
c.innerHTML = `<p class="text-gray-400 text-center py-8 text-sm">${TYPE_LABEL[currentIssueType] || currentIssueType} 유형의 카테고리가 없습니다.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
c.innerHTML = filtered.map(cat => {
|
||||
const items = issueItems
|
||||
.filter(item => item.category_id === cat.category_id)
|
||||
.sort((a, b) => (a.display_order || 0) - (b.display_order || 0));
|
||||
const inactive = cat.is_active === 0 || cat.is_active === false;
|
||||
|
||||
return `
|
||||
<div class="border rounded-lg ${inactive ? 'opacity-60 border-gray-200' : 'border-gray-200'}">
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-t-lg">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-semibold text-gray-800"><i class="fas fa-layer-group mr-1.5 text-gray-400 text-xs"></i>${escHtml(cat.category_name)}</div>
|
||||
<div class="text-xs text-gray-500 flex items-center gap-1.5 mt-0.5">
|
||||
${cat.description ? `<span>${escHtml(cat.description)}</span>` : ''}
|
||||
<span class="text-gray-400">순서: ${cat.display_order || 0}</span>
|
||||
${inactive ? '<span class="px-1.5 py-0.5 rounded bg-gray-100 text-gray-400">비활성</span>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-1 ml-2 flex-shrink-0">
|
||||
<button onclick="editIssueCategory(${cat.category_id})" class="p-1.5 text-slate-500 hover:text-slate-700 hover:bg-slate-200 rounded" title="편집"><i class="fas fa-pen-to-square text-xs"></i></button>
|
||||
<button onclick="deleteIssueCategory(${cat.category_id},'${escHtml(cat.category_name).replace(/'/g, "\\'")}')" class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-100 rounded" title="삭제"><i class="fas fa-trash text-xs"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
${items.length ? `<div class="divide-y divide-gray-100">${items.map(item => {
|
||||
const itemInactive = item.is_active === 0 || item.is_active === false;
|
||||
return `
|
||||
<div class="flex items-center justify-between px-3 py-2 ${itemInactive ? 'opacity-50' : ''}">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm text-gray-700">${escHtml(item.item_name)}</div>
|
||||
<div class="text-xs text-gray-400 flex items-center gap-1.5 mt-0.5">
|
||||
${item.description ? `<span>${escHtml(item.description)}</span>` : ''}
|
||||
<span class="px-1.5 py-0.5 rounded ${SEVERITY_CLASS[item.severity] || 'bg-gray-50 text-gray-500'}">${SEVERITY_LABEL[item.severity] || item.severity || '-'}</span>
|
||||
${itemInactive ? '<span class="px-1.5 py-0.5 rounded bg-gray-100 text-gray-400">비활성</span>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-1 ml-2 flex-shrink-0">
|
||||
<button onclick="editIssueItem(${item.item_id})" class="p-1 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded" title="편집"><i class="fas fa-pen-to-square text-xs"></i></button>
|
||||
<button onclick="deleteIssueItem(${item.item_id},'${escHtml(item.item_name).replace(/'/g, "\\'")}')" class="p-1 text-red-300 hover:text-red-500 hover:bg-red-50 rounded" title="삭제"><i class="fas fa-trash text-xs"></i></button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('')}</div>` : '<div class="px-3 py-2 text-xs text-gray-400">등록된 아이템이 없습니다.</div>'}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ===== Category CRUD =====
|
||||
|
||||
document.getElementById('addIssueCategoryForm').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await api('/work-issues/categories', { method: 'POST', body: JSON.stringify({
|
||||
category_type: document.querySelector('input[name="newIssueCatType"]:checked').value,
|
||||
category_name: document.getElementById('newIssueCatName').value.trim(),
|
||||
description: document.getElementById('newIssueCatDesc').value.trim() || null,
|
||||
display_order: parseInt(document.getElementById('newIssueCatOrder').value) || 0
|
||||
})});
|
||||
showToast('카테고리가 추가되었습니다.');
|
||||
document.getElementById('addIssueCategoryForm').reset();
|
||||
await loadIssueTypes();
|
||||
} catch(e) { showToast(e.message, 'error'); }
|
||||
});
|
||||
|
||||
function editIssueCategory(id) {
|
||||
const cat = issueCategories.find(c => c.category_id === id); if (!cat) return;
|
||||
document.getElementById('editIssueCatId').value = cat.category_id;
|
||||
document.getElementById('editIssueCatName').value = cat.category_name;
|
||||
document.getElementById('editIssueCatDesc').value = cat.description || '';
|
||||
document.getElementById('editIssueCatOrder').value = cat.display_order || 0;
|
||||
document.getElementById('editIssueCatActive').value = (cat.is_active === 0 || cat.is_active === false) ? '0' : '1';
|
||||
document.getElementById('editIssueCategoryModal').classList.remove('hidden');
|
||||
}
|
||||
function closeIssueCategoryModal() { document.getElementById('editIssueCategoryModal').classList.add('hidden'); }
|
||||
|
||||
document.getElementById('editIssueCategoryForm').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await api(`/work-issues/categories/${document.getElementById('editIssueCatId').value}`, { method: 'PUT', body: JSON.stringify({
|
||||
category_name: document.getElementById('editIssueCatName').value.trim(),
|
||||
description: document.getElementById('editIssueCatDesc').value.trim() || null,
|
||||
display_order: parseInt(document.getElementById('editIssueCatOrder').value) || 0,
|
||||
is_active: document.getElementById('editIssueCatActive').value === '1'
|
||||
})});
|
||||
showToast('수정되었습니다.'); closeIssueCategoryModal(); await loadIssueTypes();
|
||||
} catch(e) { showToast(e.message, 'error'); }
|
||||
});
|
||||
|
||||
async function deleteIssueCategory(id, name) {
|
||||
if (!confirm(`"${name}" 카테고리를 삭제하시겠습니까?\n소속 아이템도 모두 삭제됩니다.`)) return;
|
||||
try { await api(`/work-issues/categories/${id}`, { method: 'DELETE' }); showToast('카테고리 삭제 완료'); await loadIssueTypes(); } catch(e) { showToast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
// ===== Item CRUD =====
|
||||
|
||||
document.getElementById('addIssueItemForm').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await api('/work-issues/items', { method: 'POST', body: JSON.stringify({
|
||||
category_id: parseInt(document.getElementById('newIssueItemCategory').value),
|
||||
item_name: document.getElementById('newIssueItemName').value.trim(),
|
||||
description: document.getElementById('newIssueItemDesc').value.trim() || null,
|
||||
severity: document.getElementById('newIssueItemSeverity').value,
|
||||
display_order: parseInt(document.getElementById('newIssueItemOrder').value) || 0
|
||||
})});
|
||||
showToast('아이템이 추가되었습니다.');
|
||||
document.getElementById('addIssueItemForm').reset();
|
||||
await loadIssueTypes();
|
||||
} catch(e) { showToast(e.message, 'error'); }
|
||||
});
|
||||
|
||||
function editIssueItem(id) {
|
||||
const item = issueItems.find(i => i.item_id === id); if (!item) return;
|
||||
document.getElementById('editIssueItemId').value = item.item_id;
|
||||
populateIssueCategorySelects();
|
||||
document.getElementById('editIssueItemCategory').value = item.category_id || '';
|
||||
document.getElementById('editIssueItemName').value = item.item_name;
|
||||
document.getElementById('editIssueItemDesc').value = item.description || '';
|
||||
document.getElementById('editIssueItemSeverity').value = item.severity || 'medium';
|
||||
document.getElementById('editIssueItemOrder').value = item.display_order || 0;
|
||||
document.getElementById('editIssueItemActive').value = (item.is_active === 0 || item.is_active === false) ? '0' : '1';
|
||||
document.getElementById('editIssueItemModal').classList.remove('hidden');
|
||||
}
|
||||
function closeIssueItemModal() { document.getElementById('editIssueItemModal').classList.add('hidden'); }
|
||||
|
||||
document.getElementById('editIssueItemForm').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await api(`/work-issues/items/${document.getElementById('editIssueItemId').value}`, { method: 'PUT', body: JSON.stringify({
|
||||
category_id: parseInt(document.getElementById('editIssueItemCategory').value),
|
||||
item_name: document.getElementById('editIssueItemName').value.trim(),
|
||||
description: document.getElementById('editIssueItemDesc').value.trim() || null,
|
||||
severity: document.getElementById('editIssueItemSeverity').value,
|
||||
display_order: parseInt(document.getElementById('editIssueItemOrder').value) || 0,
|
||||
is_active: document.getElementById('editIssueItemActive').value === '1'
|
||||
})});
|
||||
showToast('수정되었습니다.'); closeIssueItemModal(); await loadIssueTypes();
|
||||
} catch(e) { showToast(e.message, 'error'); }
|
||||
});
|
||||
|
||||
async function deleteIssueItem(id, name) {
|
||||
if (!confirm(`"${name}" 아이템을 삭제하시겠습니까?`)) return;
|
||||
try { await api(`/work-issues/items/${id}`, { method: 'DELETE' }); showToast('아이템 삭제 완료'); await loadIssueTypes(); } catch(e) { showToast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
@@ -21,5 +21,6 @@ function switchTab(name) {
|
||||
if (name === 'workplaces' && !workplacesLoaded) loadWorkplaces();
|
||||
if (name === 'tasks' && !tasksLoaded) loadTasksTab();
|
||||
if (name === 'vacations' && !vacationsLoaded) loadVacationsTab();
|
||||
if (name === 'issueTypes' && !issueTypesLoaded) loadIssueTypes();
|
||||
if (name === 'permissions' && !permissionsTabLoaded) loadPermissionsTab();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user