/* ===== tkuser 소모품 마스터 CRUD ===== */ let consumablesLoaded = false; let consumablesList = []; // 카테고리 — system1-factory API에서 동적 로드 (fallback: 기본 4개) const TKFB_API = location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net/api' : location.protocol + '//' + location.hostname + ':30005/api'; let _consumableCategories = null; const DEFAULT_CATEGORIES = [ { category_code: 'consumable', category_name: '소모품', icon: 'fa-box', color_bg: '#dbeafe', color_fg: '#1e40af' }, { category_code: 'safety', category_name: '안전용품', icon: 'fa-hard-hat', color_bg: '#dcfce7', color_fg: '#166534' }, { category_code: 'repair', category_name: '수선비', icon: 'fa-wrench', color_bg: '#fef3c7', color_fg: '#92400e' }, { category_code: 'equipment', category_name: '설비', icon: 'fa-cogs', color_bg: '#f3e8ff', color_fg: '#7e22ce' } ]; async function loadConsumableCategories() { if (_consumableCategories) return _consumableCategories; try { const token = getToken(); const res = await fetch(TKFB_API + '/consumable-categories', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); _consumableCategories = data.data || DEFAULT_CATEGORIES; } else { _consumableCategories = DEFAULT_CATEGORIES; } } catch (e) { _consumableCategories = DEFAULT_CATEGORIES; } return _consumableCategories; } function getCatLabel(code) { return (_consumableCategories || DEFAULT_CATEGORIES).find(c => c.category_code === code)?.category_name || code; } function getCatColorClass(code) { const c = (_consumableCategories || DEFAULT_CATEGORIES).find(x => x.category_code === code); if (!c) return 'bg-gray-50 text-gray-600'; // 인라인 스타일 반환 return ''; } function getCatStyle(code) { const c = (_consumableCategories || DEFAULT_CATEGORIES).find(x => x.category_code === code); return c ? `background:${c.color_bg};color:${c.color_fg}` : 'background:#f3f4f6;color:#6b7280'; } async function loadConsumablesTab() { if (consumablesLoaded) return; consumablesLoaded = true; await loadConsumableCategories(); if (currentUser && ['admin', 'system'].includes(currentUser.role)) { document.getElementById('btnAddConsumableTkuser')?.classList.remove('hidden'); document.getElementById('categoryManageSection')?.classList.remove('hidden'); } populateCategorySelects(); await loadConsumablesList(); loadCategoryManagement(); } // 카테고리 select 옵션 동적 생성 function populateCategorySelects() { const cats = _consumableCategories || DEFAULT_CATEGORIES; const optionsHtml = cats.map(c => ``).join(''); ['consumableFilterCategoryTkuser', 'newConsumableCategoryTkuser', 'editConsumableCategoryTkuser'].forEach(id => { const el = document.getElementById(id); if (!el) return; const firstOpt = el.querySelector('option:first-child'); el.innerHTML = (firstOpt ? firstOpt.outerHTML : '') + optionsHtml; }); } async function loadConsumablesList() { try { const category = document.getElementById('consumableFilterCategoryTkuser')?.value || ''; const isActive = document.getElementById('consumableFilterActiveTkuser')?.value; const search = document.getElementById('consumableSearchTkuser')?.value?.trim() || ''; const params = new URLSearchParams(); if (category) params.set('category', category); if (isActive !== '' && isActive !== undefined) params.set('is_active', isActive); if (search) params.set('search', search); const r = await api('/consumable-items?' + params.toString()); consumablesList = r.data || []; renderConsumablesListTkuser(); } catch (e) { document.getElementById('consumablesListTkuser').innerHTML = `

${e.message}

`; } } function renderConsumablesListTkuser() { const c = document.getElementById('consumablesListTkuser'); if (!consumablesList.length) { c.innerHTML = '

등록된 소모품이 없습니다.

'; return; } const isAdmin = currentUser && ['admin', 'system'].includes(currentUser.role); c.innerHTML = `
` + consumablesList.map(item => { const catLabel = getCatLabel(item.category); const catStyle = getCatStyle(item.category); const price = item.base_price ? Number(item.base_price).toLocaleString() + '원' : '-'; return `
${item.photo_path ? `` : `
`}
${escHtml(item.item_name)}${item.spec ? ' [' + escHtml(item.spec) + ']' : ''}
${escHtml(item.maker) || '-'}
${catLabel} ${price} ${escHtml(item.unit) || 'EA'}
${!item.is_active ? '비활성' : ''}
${isAdmin ? `
${item.is_active ? `` : ''}
` : ''}
`; }).join('') + `
`; } /* ===== 소모품 등록 ===== */ function openAddConsumableTkuser() { document.getElementById('addConsumablePhotoPreviewTkuser').innerHTML = ''; document.getElementById('addConsumableModalTkuser').classList.remove('hidden'); } function closeAddConsumableTkuser() { document.getElementById('addConsumableModalTkuser').classList.add('hidden'); document.getElementById('addConsumableFormTkuser').reset(); document.getElementById('addConsumablePhotoPreviewTkuser').innerHTML = ''; } function previewAddConsumablePhoto() { const file = document.getElementById('newConsumablePhotoTkuser').files[0]; const preview = document.getElementById('addConsumablePhotoPreviewTkuser'); if (!file) { preview.innerHTML = ''; return; } const reader = new FileReader(); reader.onload = e => { preview.innerHTML = ``; }; reader.readAsDataURL(file); } async function submitAddConsumableTkuser(e) { e.preventDefault(); const itemName = document.getElementById('newConsumableNameTkuser').value.trim(); const category = document.getElementById('newConsumableCategoryTkuser').value; if (!itemName) { showToast('품명은 필수입니다', 'error'); return; } if (!category) { showToast('분류는 필수입니다', 'error'); return; } const fd = new FormData(); fd.append('item_name', itemName); fd.append('spec', document.getElementById('newConsumableSpecTkuser').value.trim()); fd.append('maker', document.getElementById('newConsumableMakerTkuser').value.trim()); fd.append('category', category); fd.append('base_price', document.getElementById('newConsumablePriceTkuser').value || '0'); fd.append('unit', document.getElementById('newConsumableUnitTkuser').value.trim() || 'EA'); const photoFile = document.getElementById('newConsumablePhotoTkuser').files[0]; if (photoFile) fd.append('photo', photoFile); try { const token = getToken(); const res = await fetch('/api/consumable-items', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: fd }); const data = await res.json(); if (!res.ok) throw new Error(data.error || '등록 실패'); showToast('소모품이 등록되었습니다'); closeAddConsumableTkuser(); await loadConsumablesList(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 소모품 수정 ===== */ function openEditConsumableTkuser(id) { const item = consumablesList.find(x => x.item_id === id); if (!item) return; document.getElementById('editConsumableIdTkuser').value = item.item_id; document.getElementById('editConsumableNameTkuser').value = item.item_name; document.getElementById('editConsumableSpecTkuser').value = item.spec || ''; document.getElementById('editConsumableMakerTkuser').value = item.maker || ''; document.getElementById('editConsumableCategoryTkuser').value = item.category; document.getElementById('editConsumablePriceTkuser').value = item.base_price || ''; document.getElementById('editConsumableUnitTkuser').value = item.unit || 'EA'; const preview = document.getElementById('editConsumablePhotoPreviewTkuser'); preview.innerHTML = item.photo_path ? `` : ''; document.getElementById('editConsumablePhotoTkuser').value = ''; document.getElementById('editConsumableModalTkuser').classList.remove('hidden'); } function closeEditConsumableTkuser() { document.getElementById('editConsumableModalTkuser').classList.add('hidden'); } function previewEditConsumablePhoto() { const file = document.getElementById('editConsumablePhotoTkuser').files[0]; const preview = document.getElementById('editConsumablePhotoPreviewTkuser'); if (!file) return; const reader = new FileReader(); reader.onload = e => { preview.innerHTML = ``; }; reader.readAsDataURL(file); } async function submitEditConsumableTkuser(e) { e.preventDefault(); const id = document.getElementById('editConsumableIdTkuser').value; const fd = new FormData(); fd.append('item_name', document.getElementById('editConsumableNameTkuser').value.trim()); fd.append('spec', document.getElementById('editConsumableSpecTkuser').value.trim()); fd.append('maker', document.getElementById('editConsumableMakerTkuser').value.trim()); fd.append('category', document.getElementById('editConsumableCategoryTkuser').value); fd.append('base_price', document.getElementById('editConsumablePriceTkuser').value || '0'); fd.append('unit', document.getElementById('editConsumableUnitTkuser').value.trim() || 'EA'); const photoFile = document.getElementById('editConsumablePhotoTkuser').files[0]; if (photoFile) fd.append('photo', photoFile); try { const token = getToken(); const res = await fetch(`/api/consumable-items/${id}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}` }, body: fd }); const data = await res.json(); if (!res.ok) throw new Error(data.error || '수정 실패'); showToast('수정되었습니다'); closeEditConsumableTkuser(); await loadConsumablesList(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 소모품 비활성화 ===== */ async function deactivateConsumableTkuser(id, name) { if (!confirm(`"${name}" 소모품을 비활성화하시겠습니까?`)) return; try { await api(`/consumable-items/${id}`, { method: 'DELETE' }); showToast('비활성화 완료'); await loadConsumablesList(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 카테고리 관리 ===== */ async function loadCategoryManagement() { const container = document.getElementById('categoryList'); if (!container) return; const cats = _consumableCategories || DEFAULT_CATEGORIES; container.innerHTML = cats.map(c => `
${escHtml(c.category_name)} (${escHtml(c.category_code)})
${c.category_id ? `` : ''}
`).join(''); } async function addCategory() { const code = prompt('카테고리 코드 (영문):'); if (!code) return; const name = prompt('표시 이름:'); if (!name) return; try { const token = getToken(); const res = await fetch(TKFB_API + '/consumable-categories', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ category_code: code, category_name: name }) }); const data = await res.json(); if (!res.ok) throw new Error(data.message || '추가 실패'); showToast('카테고리가 추가되었습니다'); _consumableCategories = null; await loadConsumableCategories(); populateCategorySelects(); loadCategoryManagement(); } catch (e) { showToast(e.message, 'error'); } } // 검색/필터 이벤트 + 모달 폼 이벤트 document.addEventListener('DOMContentLoaded', () => { let searchTimeout; const searchEl = document.getElementById('consumableSearchTkuser'); if (searchEl) searchEl.addEventListener('input', () => { clearTimeout(searchTimeout); searchTimeout = setTimeout(loadConsumablesList, 300); }); const filterCatEl = document.getElementById('consumableFilterCategoryTkuser'); if (filterCatEl) filterCatEl.addEventListener('change', loadConsumablesList); const filterActiveEl = document.getElementById('consumableFilterActiveTkuser'); if (filterActiveEl) filterActiveEl.addEventListener('change', loadConsumablesList); document.getElementById('addConsumableFormTkuser')?.addEventListener('submit', submitAddConsumableTkuser); document.getElementById('editConsumableFormTkuser')?.addEventListener('submit', submitEditConsumableTkuser); });