- 모바일 검색 결과에 품목 사진 썸네일 표시 (photo_path 있으면 이미지, 없으면 아이콘) - 데스크탑 검색 드롭다운에도 사진 썸네일 추가 - 신규 품목 등록 시 사진 촬영 → consumable_items.photo_path에 저장 (bulk API) - 기존 품목에 사진 없을 때 장바구니에서 "품목 사진 등록" → PUT /consumable-items/:id/photo - imageUploadService에 consumables 디렉토리 추가 - HEIC 변환 + 폴백 지원 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
484 lines
22 KiB
JavaScript
484 lines
22 KiB
JavaScript
/* ===== 소모품 신청 모바일 (장바구니 방식) ===== */
|
||
const TKUSER_BASE_URL = location.hostname.includes('technicalkorea.net')
|
||
? 'https://tkuser.technicalkorea.net'
|
||
: location.protocol + '//' + location.hostname + ':30180';
|
||
|
||
const CAT_LABELS = { consumable: '소모품', safety: '안전용품', repair: '수선비', equipment: '설비' };
|
||
const STATUS_LABELS = { pending: '대기', grouped: '구매진행중', purchased: '구매완료', received: '입고완료', cancelled: '취소', returned: '반품', hold: '보류' };
|
||
const STATUS_COLORS = { pending: 'badge-amber', grouped: 'badge-blue', purchased: 'badge-green', received: 'badge-teal', cancelled: 'badge-red', returned: 'badge-red', hold: 'badge-gray' };
|
||
const MATCH_LABELS = { exact: '정확', name: '이름', alias: '별칭', spec: '규격', chosung: '초성', chosung_alias: '초성' };
|
||
|
||
let currentPage = 1;
|
||
let currentStatus = '';
|
||
let totalPages = 1;
|
||
let isLoadingMore = false;
|
||
let requestsList = [];
|
||
let photoBase64 = null;
|
||
let searchTimer = null;
|
||
let lastSearchResults = [];
|
||
let cartItems = []; // [{item_id, item_name, spec, maker, category, quantity, notes, is_new, ...}]
|
||
|
||
/* ===== 상태 탭 필터 ===== */
|
||
function filterByStatus(btn) {
|
||
document.querySelectorAll('.pm-tab').forEach(t => t.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
currentStatus = btn.dataset.status;
|
||
currentPage = 1;
|
||
requestsList = [];
|
||
loadRequests();
|
||
}
|
||
|
||
/* ===== 신청 목록 로드 ===== */
|
||
async function loadRequests(append = false) {
|
||
try {
|
||
if (!append) {
|
||
document.getElementById('requestCards').innerHTML = '<div class="pm-loading">불러오는 중...</div>';
|
||
}
|
||
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; }
|
||
renderCards();
|
||
} catch (e) {
|
||
document.getElementById('requestCards').innerHTML = `<div class="pm-empty"><i class="fas fa-exclamation-circle"></i>${escapeHtml(e.message)}</div>`;
|
||
}
|
||
}
|
||
|
||
function renderCards() {
|
||
const container = document.getElementById('requestCards');
|
||
if (!requestsList.length) {
|
||
container.innerHTML = '<div class="pm-empty"><i class="fas fa-box-open"></i>신청 내역이 없습니다.</div>';
|
||
return;
|
||
}
|
||
container.innerHTML = requestsList.map(r => {
|
||
const itemName = r.item_name || r.custom_item_name || '-';
|
||
const category = r.category || r.custom_category;
|
||
const catLabel = CAT_LABELS[category] || category || '';
|
||
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 `<div class="pm-card" onclick="openDetail(${r.request_id})">
|
||
<div class="pm-card-header">
|
||
<div>
|
||
<div class="pm-card-name">${escapeHtml(itemName)}${isCustom ? '<span class="pm-card-custom">(직접입력)</span>' : ''}</div>
|
||
${catLabel ? `<span class="badge ${STATUS_COLORS[category] || 'badge-gray'} mt-1" style="font-size:11px">${catLabel}</span>` : ''}
|
||
</div>
|
||
<span class="badge ${statusColor}">${statusLabel}</span>
|
||
</div>
|
||
<div class="pm-card-meta">
|
||
<span>수량: <span class="pm-card-qty">${r.quantity}</span></span>
|
||
<span>${formatDate(r.request_date)}</span>
|
||
${r.batch_name ? `<span><i class="fas fa-layer-group"></i> ${escapeHtml(r.batch_name)}</span>` : ''}
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
/* ===== 무한 스크롤 ===== */
|
||
window.addEventListener('scroll', () => {
|
||
if (isLoadingMore || currentPage >= totalPages) return;
|
||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 200) {
|
||
isLoadingMore = true;
|
||
currentPage++;
|
||
document.getElementById('loadingMore').classList.remove('hidden');
|
||
loadRequests(true).finally(() => {
|
||
isLoadingMore = false;
|
||
document.getElementById('loadingMore').classList.add('hidden');
|
||
});
|
||
}
|
||
});
|
||
|
||
/* ===== 상세 바텀시트 ===== */
|
||
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 || '-';
|
||
const statusLabel = STATUS_LABELS[r.status] || r.status;
|
||
const statusColor = STATUS_COLORS[r.status] || 'badge-gray';
|
||
|
||
let html = `
|
||
<div class="text-center mb-4">
|
||
<div class="text-lg font-bold">${escapeHtml(itemName)}</div>
|
||
<span class="badge ${statusColor} mt-2">${statusLabel}</span>
|
||
</div>
|
||
<div class="pm-detail-row"><span class="pm-detail-label">분류</span><span class="pm-detail-value">${catLabel}</span></div>
|
||
<div class="pm-detail-row"><span class="pm-detail-label">수량</span><span class="pm-detail-value">${r.quantity}</span></div>
|
||
<div class="pm-detail-row"><span class="pm-detail-label">신청일</span><span class="pm-detail-value">${formatDate(r.request_date)}</span></div>`;
|
||
if (r.notes) html += `<div class="pm-detail-row"><span class="pm-detail-label">메모</span><span class="pm-detail-value">${escapeHtml(r.notes)}</span></div>`;
|
||
if (r.batch_name) html += `<div class="pm-detail-row"><span class="pm-detail-label">구매 그룹</span><span class="pm-detail-value">${escapeHtml(r.batch_name)}</span></div>`;
|
||
if (r.hold_reason) html += `<div class="pm-detail-row"><span class="pm-detail-label">보류 사유</span><span class="pm-detail-value text-red-600">${escapeHtml(r.hold_reason)}</span></div>`;
|
||
if (r.status === 'received') {
|
||
html += `<div class="mt-4 p-3 bg-teal-50 rounded-lg"><div class="text-sm font-semibold text-teal-700 mb-2"><i class="fas fa-box-open mr-1"></i>입고 완료</div>`;
|
||
if (r.received_location) html += `<div class="text-sm text-gray-700"><i class="fas fa-map-marker-alt mr-1 text-teal-500"></i>보관위치: ${escapeHtml(r.received_location)}</div>`;
|
||
if (r.received_at) html += `<div class="text-xs text-gray-500 mt-1">${formatDateTime(r.received_at)}${r.received_by_name ? ' · ' + escapeHtml(r.received_by_name) : ''}</div>`;
|
||
if (r.received_photo_path) html += `<img src="${r.received_photo_path}" class="pm-received-photo" onerror="this.style.display='none'">`;
|
||
html += `</div>`;
|
||
}
|
||
if (r.pr_photo_path) html += `<div class="mt-3"><div class="text-xs text-gray-500 mb-1">첨부 사진</div><img src="${r.pr_photo_path}" class="pm-received-photo" onerror="this.style.display='none'"></div>`;
|
||
|
||
document.getElementById('detailContent').innerHTML = html;
|
||
document.getElementById('detailOverlay').classList.add('open');
|
||
document.getElementById('detailSheet').classList.add('open');
|
||
}
|
||
|
||
function closeDetailSheet() {
|
||
document.getElementById('detailOverlay').classList.remove('open');
|
||
document.getElementById('detailSheet').classList.remove('open');
|
||
}
|
||
|
||
/* ===== 신청 바텀시트 ===== */
|
||
function openRequestSheet() {
|
||
document.getElementById('requestOverlay').classList.add('open');
|
||
document.getElementById('requestSheet').classList.add('open');
|
||
document.getElementById('searchInput').focus();
|
||
}
|
||
|
||
function closeRequestSheet() {
|
||
document.getElementById('requestOverlay').classList.remove('open');
|
||
document.getElementById('requestSheet').classList.remove('open');
|
||
resetRequestForm();
|
||
}
|
||
|
||
function resetRequestForm() {
|
||
document.getElementById('searchInput').value = '';
|
||
document.getElementById('searchResults').classList.remove('open');
|
||
cartItems = [];
|
||
renderCart();
|
||
document.getElementById('reqPhotoInput').value = '';
|
||
document.getElementById('reqPhotoPreview').classList.add('hidden');
|
||
document.getElementById('photoLabel').textContent = '사진 촬영/선택';
|
||
photoBase64 = null;
|
||
updateSubmitBtn();
|
||
}
|
||
|
||
/* ===== 서버 스마트 검색 ===== */
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const input = document.getElementById('searchInput');
|
||
input.addEventListener('input', () => {
|
||
clearTimeout(searchTimer);
|
||
const q = input.value.trim();
|
||
if (q.length === 0) {
|
||
document.getElementById('searchResults').classList.remove('open');
|
||
document.getElementById('searchSpinner').classList.remove('show');
|
||
return;
|
||
}
|
||
document.getElementById('searchSpinner').classList.add('show');
|
||
searchTimer = setTimeout(async () => {
|
||
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'); }
|
||
}, 300);
|
||
});
|
||
});
|
||
|
||
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) + ')' : '';
|
||
const photoUrl = item.photo_path ? (item.photo_path.startsWith('http') ? item.photo_path : TKUSER_BASE_URL + item.photo_path) : '';
|
||
html += `<div class="pm-search-item" onclick="addToCart(${item.item_id})">
|
||
${photoUrl ? `<img src="${photoUrl}" class="pm-search-thumb" onerror="this.style.display='none'">` : '<div class="pm-search-thumb-empty"><i class="fas fa-box"></i></div>'}
|
||
<div class="flex-1">
|
||
<div class="text-sm font-medium">${escapeHtml(item.item_name)}${spec}${maker}</div>
|
||
${catLabel ? `<div class="text-xs text-gray-400 mt-0.5">${catLabel}</div>` : ''}
|
||
</div>
|
||
${matchLabel ? `<span class="match-type">${matchLabel}</span>` : ''}
|
||
</div>`;
|
||
});
|
||
html += `<div class="pm-search-register" onclick="addNewToCart()">
|
||
<i class="fas fa-plus-circle"></i>
|
||
<span>"${escapeHtml(query)}" 새 품목으로 추가</span>
|
||
</div>`;
|
||
container.innerHTML = html;
|
||
container.classList.add('open');
|
||
}
|
||
|
||
/* ===== 장바구니 ===== */
|
||
function addToCart(itemId) {
|
||
const item = lastSearchResults.find(i => i.item_id === itemId);
|
||
if (!item) return;
|
||
|
||
// 동일 품목이면 수량 +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,
|
||
photo_path: item.photo_path,
|
||
quantity: 1,
|
||
notes: '',
|
||
is_new: false
|
||
});
|
||
}
|
||
document.getElementById('searchInput').value = '';
|
||
document.getElementById('searchResults').classList.remove('open');
|
||
renderCart();
|
||
updateSubmitBtn();
|
||
}
|
||
|
||
function addNewToCart() {
|
||
const query = document.getElementById('searchInput').value.trim();
|
||
if (!query) return;
|
||
cartItems.push({
|
||
item_id: null,
|
||
item_name: query,
|
||
spec: '',
|
||
maker: '',
|
||
category: '',
|
||
photo_path: null,
|
||
item_photo: null, // base64 — 마스터 사진으로 저장될 사진
|
||
quantity: 1,
|
||
notes: '',
|
||
is_new: true
|
||
});
|
||
document.getElementById('searchInput').value = '';
|
||
document.getElementById('searchResults').classList.remove('open');
|
||
renderCart();
|
||
updateSubmitBtn();
|
||
}
|
||
|
||
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 thumbHtml = '';
|
||
if (c.item_photo) {
|
||
thumbHtml = `<img src="${c.item_photo}" class="pm-cart-thumb">`;
|
||
} else if (c.photo_path) {
|
||
const url = c.photo_path.startsWith('http') ? c.photo_path : TKUSER_BASE_URL + c.photo_path;
|
||
thumbHtml = `<img src="${url}" class="pm-cart-thumb" onerror="this.style.display='none'">`;
|
||
}
|
||
|
||
let extraFields = '';
|
||
if (c.is_new) {
|
||
extraFields = `<div class="pm-cart-new-fields">
|
||
<input type="text" placeholder="규격" value="${escapeHtml(c.spec || '')}" oninput="updateCartNewField(${idx},'spec',this.value)">
|
||
<input type="text" placeholder="제조사" value="${escapeHtml(c.maker || '')}" oninput="updateCartNewField(${idx},'maker',this.value)">
|
||
<select onchange="updateCartNewField(${idx},'category',this.value)">
|
||
<option value="">분류</option>
|
||
<option value="consumable" ${c.category==='consumable'?'selected':''}>소모품</option>
|
||
<option value="safety" ${c.category==='safety'?'selected':''}>안전용품</option>
|
||
<option value="repair" ${c.category==='repair'?'selected':''}>수선비</option>
|
||
<option value="equipment" ${c.category==='equipment'?'selected':''}>설비</option>
|
||
</select>
|
||
</div>
|
||
<div class="pm-cart-new-fields" style="margin-top:4px">
|
||
<label class="pm-cart-photo-btn">
|
||
<i class="fas fa-camera"></i> ${c.item_photo ? '사진 변경' : '품목 사진'}
|
||
<input type="file" accept="image/*,.heic,.heif" capture="environment" class="hidden" onchange="onItemPhotoSelected(${idx},this)">
|
||
</label>
|
||
</div>`;
|
||
} else if (!c.photo_path) {
|
||
// 기존 품목이지만 사진 없음 → 사진 등록 가능
|
||
extraFields = `<div class="pm-cart-new-fields" style="margin-top:4px">
|
||
<label class="pm-cart-photo-btn">
|
||
<i class="fas fa-camera"></i> 품목 사진 등록
|
||
<input type="file" accept="image/*,.heic,.heif" capture="environment" class="hidden" onchange="uploadExistingItemPhoto(${idx},this)">
|
||
</label>
|
||
</div>`;
|
||
}
|
||
|
||
return `<div class="pm-cart-item">
|
||
${thumbHtml}
|
||
<div class="pm-cart-item-info">
|
||
<div class="pm-cart-item-name">${escapeHtml(c.item_name)}${spec}${maker}</div>
|
||
<div class="pm-cart-item-meta">${catLabel}${c.is_new ? ' <span class="pm-cart-item-new">(신규등록)</span>' : ''}</div>
|
||
${extraFields}
|
||
</div>
|
||
<input type="number" class="pm-cart-qty" value="${c.quantity}" min="1" inputmode="numeric" onchange="updateCartQty(${idx},this.value)">
|
||
<input type="text" class="pm-cart-memo" placeholder="메모" value="${escapeHtml(c.notes || '')}" oninput="updateCartNotes(${idx},this.value)">
|
||
<button class="pm-cart-remove" onclick="removeFromCart(${idx})">×</button>
|
||
</div>`;
|
||
}).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 = '품목을 추가해주세요';
|
||
}
|
||
}
|
||
|
||
/* ===== 품목 사진 (장바구니 내 신규/기존 품목) ===== */
|
||
async function onItemPhotoSelected(idx, 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') {
|
||
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); }
|
||
}
|
||
if (processFile.size > 10 * 1024 * 1024) { showToast('10MB 이하만 가능합니다.', 'error'); return; }
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
cartItems[idx].item_photo = e.target.result;
|
||
renderCart();
|
||
};
|
||
reader.readAsDataURL(processFile);
|
||
}
|
||
|
||
// 기존 품목(사진 없음)에 사진 업로드 → 마스터에 저장
|
||
async function uploadExistingItemPhoto(idx, 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') {
|
||
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); }
|
||
}
|
||
if (processFile.size > 10 * 1024 * 1024) { showToast('10MB 이하만 가능합니다.', 'error'); return; }
|
||
const reader = new FileReader();
|
||
reader.onload = async (e) => {
|
||
const base64 = e.target.result;
|
||
try {
|
||
const res = await api(`/purchase-requests/consumable-items/${cartItems[idx].item_id}/photo`, {
|
||
method: 'PUT', body: JSON.stringify({ photo: base64 })
|
||
});
|
||
if (res.data?.photo_path) {
|
||
cartItems[idx].photo_path = res.data.photo_path;
|
||
cartItems[idx].item_photo = null;
|
||
}
|
||
showToast('품목 사진이 등록되었습니다.');
|
||
renderCart();
|
||
} catch (err) { showToast(err.message, 'error'); }
|
||
};
|
||
reader.readAsDataURL(processFile);
|
||
}
|
||
|
||
/* ===== 참고 사진 (신청 공통) ===== */
|
||
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') {
|
||
document.getElementById('photoLabel').textContent = '변환 중...';
|
||
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); }
|
||
}
|
||
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;
|
||
document.getElementById('reqPhotoPreview').src = photoBase64;
|
||
document.getElementById('reqPhotoPreview').classList.remove('hidden');
|
||
document.getElementById('photoLabel').textContent = '사진 변경';
|
||
};
|
||
reader.readAsDataURL(processFile);
|
||
}
|
||
|
||
/* ===== 일괄 신청 제출 ===== */
|
||
async function submitRequest() {
|
||
if (cartItems.length === 0) { showToast('품목을 추가해주세요.', 'error'); return; }
|
||
|
||
const btn = document.getElementById('submitBtn');
|
||
btn.disabled = true;
|
||
btn.textContent = '처리 중...';
|
||
|
||
try {
|
||
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, item_photo: c.item_photo || null };
|
||
}
|
||
return { item_id: c.item_id, quantity: c.quantity, notes: c.notes || null };
|
||
});
|
||
const body = { items };
|
||
if (photoBase64) body.photo = photoBase64;
|
||
|
||
await api('/purchase-requests/bulk', { method: 'POST', body: JSON.stringify(body) });
|
||
showToast(`${cartItems.length}건 소모품 신청이 등록되었습니다.`);
|
||
closeRequestSheet();
|
||
currentPage = 1;
|
||
requestsList = [];
|
||
await loadRequests();
|
||
} catch (e) {
|
||
showToast(e.message, 'error');
|
||
} finally {
|
||
btn.disabled = false;
|
||
updateSubmitBtn();
|
||
}
|
||
}
|
||
|
||
/* ===== URL 파라미터로 상세 열기 ===== */
|
||
function checkViewParam() {
|
||
const urlParams = new URLSearchParams(location.search);
|
||
const viewId = urlParams.get('view');
|
||
if (viewId) setTimeout(() => openDetail(parseInt(viewId)), 500);
|
||
}
|
||
|
||
/* ===== Init ===== */
|
||
(async function() {
|
||
if (!await initAuth()) return;
|
||
await loadRequests();
|
||
checkViewParam();
|
||
})();
|