From 5832755475c875e16623fb51943ada32a53c2a57 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 1 Apr 2026 10:21:16 +0900 Subject: [PATCH] =?UTF-8?q?fix(purchase):=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=20=EC=88=98=EC=A0=95=20+=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=9E=90=EB=8F=99=ED=97=88=EC=9A=A9=20+=20?= =?UTF-8?q?=EC=9E=A5=EB=B0=94=EA=B5=AC=EB=8B=88=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=ED=92=88=EB=AA=A9=20=EC=8B=A0=EC=B2=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sideNav: TBM 패턴(hidden lg:flex + mobile-open) 적용, 회색 오버레이 버그 수정 - 권한: NAV_MENU 기반 publicPageKeys 추출, admin이 아닌 페이지는 비관리자 자동 접근 허용 - 장바구니: 검색→품목 추가→계속 검색→추가, 동일 품목 수량 합산, 품목별 메모 - bulk API: POST /purchase-requests/bulk (트랜잭션, is_new 품목 마스터 등록 포함) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../controllers/purchaseRequestController.js | 81 +++++ .../api/routes/purchaseRequestRoutes.js | 2 + system1-factory/web/css/purchase-mobile.css | 69 +++++ .../web/pages/purchase/request-mobile.html | 77 ++--- .../web/pages/purchase/request.html | 4 +- .../web/static/js/purchase-request-mobile.js | 289 +++++++++--------- system1-factory/web/static/js/tkfb-core.js | 9 +- 7 files changed, 323 insertions(+), 208 deletions(-) diff --git a/system1-factory/api/controllers/purchaseRequestController.js b/system1-factory/api/controllers/purchaseRequestController.js index 0a7657b..2769f90 100644 --- a/system1-factory/api/controllers/purchaseRequestController.js +++ b/system1-factory/api/controllers/purchaseRequestController.js @@ -180,6 +180,87 @@ const PurchaseRequestController = { } }, + // 일괄 신청 (장바구니, 트랜잭션) + bulkCreate: async (req, res) => { + const { getDb } = require('../dbPool'); + let conn; + try { + const { items, photo } = req.body; + if (!items || !Array.isArray(items) || items.length === 0) { + return res.status(400).json({ success: false, message: '신청할 품목을 선택해주세요.' }); + } + for (const item of items) { + if (!item.item_id && !item.item_name) { + return res.status(400).json({ success: false, message: '품목 정보가 올바르지 않습니다.' }); + } + if (!item.quantity || item.quantity < 1) { + return res.status(400).json({ success: false, message: '수량은 1 이상이어야 합니다.' }); + } + } + + // 사진 업로드 (트랜잭션 밖 — 파일은 롤백 불가) + let photo_path = null; + if (photo) { + photo_path = await saveBase64Image(photo, 'pr', 'purchase_requests'); + } + + const db = await getDb(); + conn = await db.getConnection(); + await conn.beginTransaction(); + + const createdIds = []; + let newItemRegistered = false; + + for (const item of items) { + let itemId = item.item_id || null; + + // 신규 품목 등록 (is_new) + if (item.is_new && item.item_name) { + const [existing] = await conn.query( + `SELECT item_id FROM consumable_items + WHERE item_name = ? AND (spec = ? OR (spec IS NULL AND ? IS NULL)) + AND (maker = ? OR (maker IS NULL AND ? IS NULL))`, + [item.item_name.trim(), item.spec || null, item.spec || null, item.maker || null, item.maker || null] + ); + if (existing.length > 0) { + itemId = existing[0].item_id; + } else { + const [ins] = await conn.query( + `INSERT INTO consumable_items (item_name, spec, maker, category, is_active) VALUES (?, ?, ?, ?, 1)`, + [item.item_name.trim(), item.spec || null, item.maker || null, item.category || 'consumable'] + ); + itemId = ins.insertId; + newItemRegistered = true; + } + } + + // purchase_request 생성 + const [result] = await conn.query( + `INSERT INTO purchase_requests (item_id, custom_item_name, custom_category, quantity, requester_id, request_date, notes, photo_path) + VALUES (?, ?, ?, ?, ?, CURDATE(), ?, ?)`, + [itemId, item.is_new && !itemId ? item.item_name : null, item.is_new && !itemId ? item.category : null, + item.quantity, req.user.id, item.notes || null, photo_path] + ); + createdIds.push(result.insertId); + } + + await conn.commit(); + if (newItemRegistered) koreanSearch.clearCache(); + + res.status(201).json({ + success: true, + data: { request_ids: createdIds, count: createdIds.length }, + message: `${createdIds.length}건의 소모품 신청이 등록되었습니다.` + }); + } catch (err) { + if (conn) await conn.rollback().catch(() => {}); + logger.error('bulkCreate error:', err); + res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' }); + } finally { + if (conn) conn.release(); + } + }, + // 스마트 검색 (초성 + 별칭 + substring) search: async (req, res) => { try { diff --git a/system1-factory/api/routes/purchaseRequestRoutes.js b/system1-factory/api/routes/purchaseRequestRoutes.js index 4f11a54..c98dcb8 100644 --- a/system1-factory/api/routes/purchaseRequestRoutes.js +++ b/system1-factory/api/routes/purchaseRequestRoutes.js @@ -15,6 +15,8 @@ router.get('/my-requests', ctrl.getMyRequests); // 품목 등록 + 신청 동시 (트랜잭션) router.post('/register-and-request', ctrl.registerAndRequest); +// 일괄 신청 (장바구니) +router.post('/bulk', ctrl.bulkCreate); // 구매신청 CRUD router.get('/', ctrl.getAll); diff --git a/system1-factory/web/css/purchase-mobile.css b/system1-factory/web/css/purchase-mobile.css index 6298360..506e37b 100644 --- a/system1-factory/web/css/purchase-mobile.css +++ b/system1-factory/web/css/purchase-mobile.css @@ -215,6 +215,75 @@ } .pm-search-register:active { background: #fff7ed; } +/* 장바구니 */ +.pm-cart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} +.pm-cart-title { font-size: 14px; font-weight: 600; color: #374151; } +.pm-cart-count { font-size: 12px; color: #ea580c; font-weight: 600; } +.pm-cart-item { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 10px; + background: #fff7ed; + border: 1px solid #fed7aa; + border-radius: 8px; + margin-bottom: 6px; +} +.pm-cart-item-info { flex: 1; min-width: 0; } +.pm-cart-item-name { font-size: 13px; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.pm-cart-item-meta { font-size: 11px; color: #9ca3af; margin-top: 2px; } +.pm-cart-item-new { font-size: 10px; color: #ea580c; } +.pm-cart-qty { + width: 48px; + padding: 4px 6px; + border: 1px solid #e5e7eb; + border-radius: 6px; + font-size: 14px; + text-align: center; + flex-shrink: 0; +} +.pm-cart-memo { + width: 80px; + padding: 4px 6px; + border: 1px solid #e5e7eb; + border-radius: 6px; + font-size: 12px; + flex-shrink: 0; +} +.pm-cart-remove { + width: 24px; + height: 24px; + border: none; + background: #fecaca; + color: #dc2626; + border-radius: 50%; + font-size: 14px; + cursor: pointer; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} +/* 신규 품목 인라인 필드 */ +.pm-cart-new-fields { + display: flex; + gap: 4px; + margin-top: 4px; +} +.pm-cart-new-fields input, .pm-cart-new-fields select { + padding: 3px 6px; + border: 1px solid #e5e7eb; + border-radius: 4px; + font-size: 11px; + width: 100%; +} + /* 폼 필드 */ .pm-field { margin-bottom: 12px; } .pm-label { display: block; font-size: 12px; font-weight: 600; color: #6b7280; margin-bottom: 4px; } diff --git a/system1-factory/web/pages/purchase/request-mobile.html b/system1-factory/web/pages/purchase/request-mobile.html index df1047a..42d02ea 100644 --- a/system1-factory/web/pages/purchase/request-mobile.html +++ b/system1-factory/web/pages/purchase/request-mobile.html @@ -6,8 +6,8 @@ 소모품 신청 - TK 공장관리 - - + + @@ -24,9 +24,9 @@ - - - + + +
@@ -62,61 +62,18 @@
- - - - - - - @@ -140,8 +97,8 @@
- - - + + + diff --git a/system1-factory/web/pages/purchase/request.html b/system1-factory/web/pages/purchase/request.html index 158eea1..ee789e6 100644 --- a/system1-factory/web/pages/purchase/request.html +++ b/system1-factory/web/pages/purchase/request.html @@ -310,7 +310,7 @@ - - + + diff --git a/system1-factory/web/static/js/purchase-request-mobile.js b/system1-factory/web/static/js/purchase-request-mobile.js index c5a9ee1..288dd1c 100644 --- a/system1-factory/web/static/js/purchase-request-mobile.js +++ b/system1-factory/web/static/js/purchase-request-mobile.js @@ -1,4 +1,4 @@ -/* ===== 소모품 신청 모바일 ===== */ +/* ===== 소모품 신청 모바일 (장바구니 방식) ===== */ const TKUSER_BASE_URL = location.hostname.includes('technicalkorea.net') ? 'https://tkuser.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30180'; @@ -15,7 +15,8 @@ let isLoadingMore = false; let requestsList = []; let photoBase64 = null; let searchTimer = null; -let isRegisterMode = false; +let lastSearchResults = []; +let cartItems = []; // [{item_id, item_name, spec, maker, category, quantity, notes, is_new, ...}] /* ===== 상태 탭 필터 ===== */ function filterByStatus(btn) { @@ -35,16 +36,10 @@ async function loadRequests(append = false) { } const params = new URLSearchParams({ page: currentPage, limit: 20 }); if (currentStatus) params.set('status', currentStatus); - const res = await api('/purchase-requests/my-requests?' + params.toString()); const items = res.data || []; totalPages = res.pagination?.totalPages || 1; - - if (append) { - requestsList = requestsList.concat(items); - } else { - requestsList = items; - } + if (append) { requestsList = requestsList.concat(items); } else { requestsList = items; } renderCards(); } catch (e) { document.getElementById('requestCards').innerHTML = `
${escapeHtml(e.message)}
`; @@ -64,7 +59,6 @@ function renderCards() { const statusLabel = STATUS_LABELS[r.status] || r.status; const statusColor = STATUS_COLORS[r.status] || 'badge-gray'; const isCustom = !r.item_id && r.custom_item_name; - return `
@@ -100,7 +94,6 @@ window.addEventListener('scroll', () => { function openDetail(requestId) { const r = requestsList.find(x => x.request_id === requestId); if (!r) return; - const itemName = r.item_name || r.custom_item_name || '-'; const category = r.category || r.custom_category; const catLabel = CAT_LABELS[category] || category || '-'; @@ -114,38 +107,18 @@ function openDetail(requestId) {
분류${catLabel}
수량${r.quantity}
-
신청일${formatDate(r.request_date)}
- `; - if (r.notes) { - html += `
메모${escapeHtml(r.notes)}
`; - } - if (r.batch_name) { - html += `
구매 그룹${escapeHtml(r.batch_name)}
`; - } - if (r.hold_reason) { - html += `
보류 사유${escapeHtml(r.hold_reason)}
`; - } - - // 입고 정보 +
신청일${formatDate(r.request_date)}
`; + if (r.notes) html += `
메모${escapeHtml(r.notes)}
`; + if (r.batch_name) html += `
구매 그룹${escapeHtml(r.batch_name)}
`; + if (r.hold_reason) html += `
보류 사유${escapeHtml(r.hold_reason)}
`; if (r.status === 'received') { - html += `
-
입고 완료
`; - if (r.received_location) { - html += `
보관위치: ${escapeHtml(r.received_location)}
`; - } - if (r.received_at) { - html += `
${formatDateTime(r.received_at)}${r.received_by_name ? ' · ' + escapeHtml(r.received_by_name) : ''}
`; - } - if (r.received_photo_path) { - html += ``; - } + html += `
입고 완료
`; + if (r.received_location) html += `
보관위치: ${escapeHtml(r.received_location)}
`; + if (r.received_at) html += `
${formatDateTime(r.received_at)}${r.received_by_name ? ' · ' + escapeHtml(r.received_by_name) : ''}
`; + if (r.received_photo_path) html += ``; html += `
`; } - - // 신청 사진 - if (r.pr_photo_path) { - html += `
첨부 사진
`; - } + if (r.pr_photo_path) html += `
첨부 사진
`; document.getElementById('detailContent').innerHTML = html; document.getElementById('detailOverlay').classList.add('open'); @@ -173,17 +146,13 @@ function closeRequestSheet() { function resetRequestForm() { document.getElementById('searchInput').value = ''; document.getElementById('searchResults').classList.remove('open'); - document.getElementById('selectedItemWrap').classList.add('hidden'); - document.getElementById('selectedItemId').value = ''; - document.getElementById('selectedCustomName').value = ''; - document.getElementById('newItemForm').classList.add('hidden'); - document.getElementById('reqQuantity').value = '1'; - document.getElementById('reqNotes').value = ''; + cartItems = []; + renderCart(); document.getElementById('reqPhotoInput').value = ''; document.getElementById('reqPhotoPreview').classList.add('hidden'); document.getElementById('photoLabel').textContent = '사진 촬영/선택'; photoBase64 = null; - isRegisterMode = false; + updateSubmitBtn(); } /* ===== 서버 스마트 검색 ===== */ @@ -191,14 +160,6 @@ document.addEventListener('DOMContentLoaded', () => { const input = document.getElementById('searchInput'); input.addEventListener('input', () => { clearTimeout(searchTimer); - // 검색어 입력 시 이전 선택 자동 해제 - if (document.getElementById('selectedItemId').value || document.getElementById('selectedCustomName').value) { - document.getElementById('selectedItemWrap').classList.add('hidden'); - document.getElementById('selectedItemId').value = ''; - document.getElementById('selectedCustomName').value = ''; - document.getElementById('newItemForm').classList.add('hidden'); - isRegisterMode = false; - } const q = input.value.trim(); if (q.length === 0) { document.getElementById('searchResults').classList.remove('open'); @@ -210,11 +171,8 @@ document.addEventListener('DOMContentLoaded', () => { try { const res = await api('/purchase-requests/search?q=' + encodeURIComponent(q)); renderSearchResults(res.data || [], q); - } catch (e) { - console.error('검색 오류:', e); - } finally { - document.getElementById('searchSpinner').classList.remove('show'); - } + } catch (e) { console.error('검색 오류:', e); } + finally { document.getElementById('searchSpinner').classList.remove('show'); } }, 300); }); }); @@ -223,13 +181,12 @@ function renderSearchResults(items, query) { lastSearchResults = items; const container = document.getElementById('searchResults'); let html = ''; - items.forEach(item => { const catLabel = CAT_LABELS[item.category] || ''; const matchLabel = MATCH_LABELS[item._matchType] || ''; const spec = item.spec ? ' [' + escapeHtml(item.spec) + ']' : ''; const maker = item.maker ? ' (' + escapeHtml(item.maker) + ')' : ''; - html += `
+ html += `
${escapeHtml(item.item_name)}${spec}${maker}
${catLabel ? `
${catLabel}
` : ''} @@ -237,57 +194,134 @@ function renderSearchResults(items, query) { ${matchLabel ? `${matchLabel}` : ''}
`; }); - - // 새 품목 등록 옵션 - html += `
+ html += `
- "${escapeHtml(query)}" 새 품목으로 등록 후 신청 + "${escapeHtml(query)}" 새 품목으로 추가
`; - container.innerHTML = html; container.classList.add('open'); } -/* ===== 품목 선택 ===== */ -let lastSearchResults = []; - -function selectSearchItem(itemId) { +/* ===== 장바구니 ===== */ +function addToCart(itemId) { const item = lastSearchResults.find(i => i.item_id === itemId); if (!item) return; - document.getElementById('selectedItemId').value = item.item_id; - document.getElementById('selectedCustomName').value = ''; - document.getElementById('selectedItemName').textContent = item.item_name + (item.spec ? ' [' + item.spec + ']' : '') + (item.maker ? ' (' + item.maker + ')' : ''); - document.getElementById('selectedItemMeta').textContent = (CAT_LABELS[item.category] || '') + (item.base_price ? ' · ' + Number(item.base_price).toLocaleString() + '원' : ''); - document.getElementById('selectedItemWrap').classList.remove('hidden'); - document.getElementById('searchResults').classList.remove('open'); + + // 동일 품목이면 수량 +1 + const existing = cartItems.find(c => c.item_id === item.item_id && !c.is_new); + if (existing) { + existing.quantity++; + showToast(`${item.item_name} 수량이 ${existing.quantity}개로 추가되었습니다.`); + } else { + cartItems.push({ + item_id: item.item_id, + item_name: item.item_name, + spec: item.spec, + maker: item.maker, + category: item.category, + quantity: 1, + notes: '', + is_new: false + }); + } document.getElementById('searchInput').value = ''; - document.getElementById('newItemForm').classList.add('hidden'); - isRegisterMode = false; + document.getElementById('searchResults').classList.remove('open'); + renderCart(); + updateSubmitBtn(); } -function selectNewItem() { +function addNewToCart() { const query = document.getElementById('searchInput').value.trim(); if (!query) return; - document.getElementById('selectedItemId').value = ''; - document.getElementById('selectedCustomName').value = query; - document.getElementById('selectedItemName').textContent = query; - document.getElementById('selectedItemMeta').textContent = '직접 입력'; - document.getElementById('selectedItemWrap').classList.remove('hidden'); + cartItems.push({ + item_id: null, + item_name: query, + spec: '', + maker: '', + category: '', + quantity: 1, + notes: '', + is_new: true + }); + document.getElementById('searchInput').value = ''; document.getElementById('searchResults').classList.remove('open'); - - // 신규 등록 폼 표시 - document.getElementById('newItemForm').classList.remove('hidden'); - document.getElementById('newItemName').value = query; - isRegisterMode = true; + renderCart(); + updateSubmitBtn(); } -function clearSelectedItem() { - document.getElementById('selectedItemWrap').classList.add('hidden'); - document.getElementById('selectedItemId').value = ''; - document.getElementById('selectedCustomName').value = ''; - document.getElementById('newItemForm').classList.add('hidden'); - document.getElementById('searchInput').value = ''; - isRegisterMode = false; +function removeFromCart(idx) { + cartItems.splice(idx, 1); + renderCart(); + updateSubmitBtn(); +} + +function updateCartQty(idx, val) { + const qty = parseInt(val) || 1; + cartItems[idx].quantity = Math.max(1, qty); +} + +function updateCartNotes(idx, val) { + cartItems[idx].notes = val; +} + +function updateCartNewField(idx, field, val) { + cartItems[idx][field] = val; +} + +function renderCart() { + const wrap = document.getElementById('cartWrap'); + const list = document.getElementById('cartList'); + const count = document.getElementById('cartCount'); + + if (cartItems.length === 0) { + wrap.classList.add('hidden'); + return; + } + wrap.classList.remove('hidden'); + count.textContent = cartItems.length + '건'; + + list.innerHTML = cartItems.map((c, idx) => { + const spec = c.spec ? ' [' + escapeHtml(c.spec) + ']' : ''; + const maker = c.maker ? ' (' + escapeHtml(c.maker) + ')' : ''; + const catLabel = CAT_LABELS[c.category] || ''; + + let newFields = ''; + if (c.is_new) { + newFields = `
+ + + +
`; + } + + return `
+
+
${escapeHtml(c.item_name)}${spec}${maker}
+
${catLabel}${c.is_new ? ' (신규등록)' : ''}
+ ${newFields} +
+ + + +
`; + }).join(''); +} + +function updateSubmitBtn() { + const btn = document.getElementById('submitBtn'); + if (cartItems.length > 0) { + btn.disabled = false; + btn.textContent = cartItems.length + '건 신청하기'; + } else { + btn.disabled = true; + btn.textContent = '품목을 추가해주세요'; + } } /* ===== 사진 ===== */ @@ -295,7 +329,6 @@ async function onMobilePhotoSelected(inputEl) { const file = inputEl.files[0]; if (!file) return; let processFile = file; - const isHeic = file.type === 'image/heic' || file.type === 'image/heif' || file.name.toLowerCase().endsWith('.heic') || file.name.toLowerCase().endsWith('.heif'); if (isHeic && typeof heic2any !== 'undefined') { @@ -303,18 +336,13 @@ async function onMobilePhotoSelected(inputEl) { try { const blob = await heic2any({ blob: file, toType: 'image/jpeg', quality: 0.85 }); processFile = new File([blob], file.name.replace(/\.hei[cf]$/i, '.jpg'), { type: 'image/jpeg' }); - } catch (e) { - console.warn('HEIC 변환 실패, 원본 사용:', e); - // 폴백: 원본 파일 그대로 사용 - } + } catch (e) { console.warn('HEIC 변환 실패, 원본 사용:', e); } } - if (processFile.size > 10 * 1024 * 1024) { showToast('파일 크기는 10MB 이하만 가능합니다.', 'error'); document.getElementById('photoLabel').textContent = '사진 촬영/선택'; return; } - const reader = new FileReader(); reader.onload = (e) => { photoBase64 = e.target.result; @@ -325,50 +353,26 @@ async function onMobilePhotoSelected(inputEl) { reader.readAsDataURL(processFile); } -/* ===== 신청 제출 ===== */ +/* ===== 일괄 신청 제출 ===== */ async function submitRequest() { - const itemId = document.getElementById('selectedItemId').value; - const customName = document.getElementById('selectedCustomName').value; - const quantity = parseInt(document.getElementById('reqQuantity').value) || 0; - - if (!itemId && !customName) { showToast('품목을 선택하거나 검색해주세요.', 'error'); return; } - if (quantity < 1) { showToast('수량은 1 이상이어야 합니다.', 'error'); return; } + if (cartItems.length === 0) { showToast('품목을 추가해주세요.', 'error'); return; } const btn = document.getElementById('submitBtn'); btn.disabled = true; btn.textContent = '처리 중...'; try { - if (isRegisterMode && customName) { - // Phase 4: 인라인 등록 + 신청 - const body = { - item_name: customName, - spec: document.getElementById('newItemSpec').value.trim() || null, - maker: document.getElementById('newItemMaker').value.trim() || null, - category: document.getElementById('newItemCategory').value || null, - quantity, - notes: document.getElementById('reqNotes').value.trim() || null - }; - if (photoBase64) body.photo = photoBase64; - await api('/purchase-requests/register-and-request', { - method: 'POST', body: JSON.stringify(body) - }); - } else { - // 기존 방식 - const body = { quantity, notes: document.getElementById('reqNotes').value.trim() }; - if (itemId) { - body.item_id = parseInt(itemId); - } else { - body.custom_item_name = customName; - body.custom_category = document.getElementById('newItemCategory')?.value || null; + const items = cartItems.map(c => { + if (c.is_new) { + return { item_name: c.item_name, spec: c.spec || null, maker: c.maker || null, category: c.category || null, quantity: c.quantity, notes: c.notes || null, is_new: true }; } - if (photoBase64) body.photo = photoBase64; - await api('/purchase-requests', { - method: 'POST', body: JSON.stringify(body) - }); - } + return { item_id: c.item_id, quantity: c.quantity, notes: c.notes || null }; + }); + const body = { items }; + if (photoBase64) body.photo = photoBase64; - showToast('소모품 신청이 등록되었습니다.'); + await api('/purchase-requests/bulk', { method: 'POST', body: JSON.stringify(body) }); + showToast(`${cartItems.length}건 소모품 신청이 등록되었습니다.`); closeRequestSheet(); currentPage = 1; requestsList = []; @@ -377,7 +381,7 @@ async function submitRequest() { showToast(e.message, 'error'); } finally { btn.disabled = false; - btn.textContent = '신청하기'; + updateSubmitBtn(); } } @@ -385,10 +389,7 @@ async function submitRequest() { function checkViewParam() { const urlParams = new URLSearchParams(location.search); const viewId = urlParams.get('view'); - if (viewId) { - // 데이터 로드 후 상세 열기 - setTimeout(() => openDetail(parseInt(viewId)), 500); - } + if (viewId) setTimeout(() => openDetail(parseInt(viewId)), 500); } /* ===== Init ===== */ diff --git a/system1-factory/web/static/js/tkfb-core.js b/system1-factory/web/static/js/tkfb-core.js index 9c955cd..643c95d 100644 --- a/system1-factory/web/static/js/tkfb-core.js +++ b/system1-factory/web/static/js/tkfb-core.js @@ -170,7 +170,6 @@ const PAGE_KEY_ALIASES = { 'attendance.work_status': 'inspection.work_status', 'work.meeting_detail': 'work.meetings', 'work.proxy_input': 'work.daily_status', - 'purchase.request_mobile': 'purchase.request', }; function _getCurrentPageKey() { @@ -283,10 +282,16 @@ async function initAuth() { let accessibleKeys = []; if (!isAdmin) { accessibleKeys = await _fetchPageAccess(currentUser.id); + // NAV_MENU에서 admin이 아닌 페이지는 모든 인증 사용자에게 공개 + const publicPageKeys = NAV_MENU.flatMap(entry => { + if (!entry.items) return entry.key ? [entry.key] : []; + if (entry.admin) return []; + return entry.items.filter(item => !item.admin).map(item => item.key); + }); // 현재 페이지 접근 권한 확인 const pageKey = _getCurrentPageKey(); if (pageKey && pageKey !== 'dashboard' && !pageKey.startsWith('profile.')) { - if (!accessibleKeys.includes(pageKey)) { + if (!publicPageKeys.includes(pageKey) && !accessibleKeys.includes(pageKey)) { alert('이 페이지에 접근할 권한이 없습니다.'); location.href = '/pages/dashboard-new.html'; return false;