Files
tk-factory-services/system1-factory/web/pages/purchase/request.html
Hyungi Ahn dcd40e692f fix(purchase): 네비 명칭/권한 수정 + 검색 재선택 가능하도록 개선
- 네비: '소모품 관리' → '소모품 구매 관리'(admin only), 일반 사용자는 '소모품 신청'만 표시
- 권한: purchase.request_mobile → purchase.request alias 등록 (비관리자 접근 가능)
- 검색: 품목 선택 후 다시 입력하면 이전 선택 자동 해제 (데스크탑+모바일)
- 모바일: selectSearchItem에서 불필요한 API 재호출 제거, 로컬 캐시 활용

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 09:50:15 +09:00

317 lines
19 KiB
HTML
Raw Blame History

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>소모품 신청 - TK 공장관리</title>
<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/tkfb.css?v=2026033108">
<script src="https://cdn.jsdelivr.net/npm/heic2any@0.0.4/dist/heic2any.min.js"></script>
<style>
.item-dropdown { position: absolute; top: 100%; left: 0; right: 0; max-height: 280px; overflow-y: auto; background: white; border: 1px solid #e5e7eb; border-top: none; border-radius: 0 0 8px 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); z-index: 20; display: none; }
.item-dropdown.open { display: block; }
.item-dropdown-item { padding: 8px 12px; cursor: pointer; font-size: 14px; display: flex; align-items: center; gap: 8px; }
.item-dropdown-item:hover, .item-dropdown-item.active { background: #fff7ed; }
.item-dropdown-item .cat-tag { font-size: 11px; padding: 1px 6px; border-radius: 4px; white-space: nowrap; }
.item-dropdown-custom { padding: 10px 12px; cursor: pointer; font-size: 14px; color: #ea580c; border-top: 1px solid #f3f4f6; display: flex; align-items: center; gap: 6px; }
.item-dropdown-custom:hover { background: #fff7ed; }
.photo-preview-container { position: relative; display: inline-block; }
.photo-preview-container .remove-btn { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%; background: #ef4444; color: white; border: none; cursor: pointer; font-size: 11px; display: flex; align-items: center; justify-content: center; }
</style>
</head>
<body class="bg-gray-50">
<header class="bg-orange-700 text-white sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-14">
<div class="flex items-center gap-3">
<button id="mobileMenuBtn" class="lg:hidden text-orange-200 hover:text-white"><i class="fas fa-bars text-xl"></i></button>
<i class="fas fa-industry text-xl text-orange-200"></i>
<h1 class="text-lg font-semibold">TK 공장관리</h1>
</div>
<div class="flex items-center gap-4">
<span id="headerUserName" class="text-sm hidden sm:block">-</span>
<div id="headerUserAvatar" class="w-8 h-8 bg-orange-600 rounded-full flex items-center justify-center text-sm font-bold">-</div>
<button onclick="doLogout()" class="text-orange-200 hover:text-white" title="로그아웃"><i class="fas fa-sign-out-alt"></i></button>
</div>
</div>
</div>
</header>
<div id="mobileOverlay" class="hidden fixed inset-0 bg-black/50 z-30 lg:hidden"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 fade-in">
<div class="flex gap-6">
<nav id="sideNav" class="hidden lg:flex flex-col gap-1 w-52 flex-shrink-0 pt-2 fixed lg:static z-40 bg-white lg:bg-transparent p-4 lg:p-0 rounded-lg lg:rounded-none shadow-lg lg:shadow-none top-14 left-0 bottom-0 overflow-y-auto"></nav>
<div class="flex-1 min-w-0">
<div class="page-header">
<div class="page-title-section">
<h1 class="page-title">소모품 신청</h1>
<p class="page-description">소모품을 신청하고 처리 현황을 확인합니다</p>
</div>
</div>
<!-- 구매신청 폼 -->
<div class="bg-white rounded-xl shadow-sm p-5 mb-6">
<h2 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-plus-circle text-orange-500 mr-2"></i>신규 소모품 신청</h2>
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4 items-start">
<div class="sm:col-span-2 relative">
<label class="block text-xs font-medium text-gray-600 mb-1">소모품 <span class="text-red-400">*</span></label>
<input type="text" id="prItemSearch" class="w-full px-3 py-2 border rounded-lg text-sm focus:ring-2 focus:ring-orange-300" placeholder="소모품 검색 또는 직접 입력" autocomplete="off">
<input type="hidden" id="prItemId" value="">
<input type="hidden" id="prCustomItemName" value="">
<div id="prItemDropdown" class="item-dropdown"></div>
<div id="prItemPreview" class="mt-2 hidden flex items-center gap-3 p-2 bg-gray-50 rounded-lg">
<img id="prItemPhoto" class="w-12 h-12 rounded object-cover hidden">
<div>
<div id="prItemInfo" class="text-sm text-gray-700"></div>
<div id="prItemPrice" class="text-xs text-gray-500"></div>
</div>
</div>
<!-- 직접 입력 시 분류 선택 -->
<div id="prCustomCategoryWrap" class="mt-2 hidden">
<label class="block text-xs font-medium text-gray-600 mb-1">분류 <span class="text-red-400">*</span></label>
<select id="prCustomCategory" class="w-full px-3 py-2 border rounded-lg text-sm">
<option value="consumable">소모품</option>
<option value="safety">안전용품</option>
<option value="repair">수선비</option>
<option value="equipment">설비</option>
</select>
</div>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">수량 <span class="text-red-400">*</span></label>
<input type="number" id="prQuantity" class="w-full px-3 py-2 border rounded-lg text-sm" min="1" value="1">
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">메모</label>
<input type="text" id="prNotes" class="w-full px-3 py-2 border rounded-lg text-sm" placeholder="선택 사항">
</div>
</div>
<!-- 사진 첨부 -->
<div class="mt-3">
<label class="block text-xs font-medium text-gray-600 mb-1">사진 첨부 (선택)</label>
<div class="flex items-center gap-3">
<label class="px-3 py-2 border rounded-lg text-sm text-gray-600 cursor-pointer hover:bg-gray-50 inline-flex items-center gap-1">
<i class="fas fa-camera text-orange-400"></i> 사진 선택
<input type="file" id="prPhotoInput" accept="image/*,.heic,.heif" class="hidden" onchange="onPhotoSelected(this)">
</label>
<div id="prPhotoPreview" class="hidden photo-preview-container">
<img id="prPhotoPreviewImg" class="w-16 h-16 rounded object-cover">
<button class="remove-btn" onclick="removePhoto()" title="삭제">&times;</button>
</div>
<span id="prPhotoStatus" class="text-xs text-gray-400"></span>
</div>
</div>
<div class="mt-4 flex justify-end">
<button onclick="submitPurchaseRequest()" class="px-5 py-2 bg-orange-600 text-white rounded-lg text-sm hover:bg-orange-700">
<i class="fas fa-paper-plane mr-1"></i>소모품 신청
</button>
</div>
</div>
<!-- 필터 -->
<div class="flex gap-3 mb-4 flex-wrap items-center">
<select id="prFilterStatus" class="px-3 py-2 border rounded-lg text-sm" onchange="loadRequests()">
<option value="">전체 상태</option>
<option value="pending">대기</option>
<option value="grouped">구매진행중</option>
<option value="purchased">구매완료</option>
<option value="received">입고완료</option>
<option value="hold">보류</option>
</select>
<select id="prFilterCategory" class="px-3 py-2 border rounded-lg text-sm" onchange="loadRequests()">
<option value="">전체 분류</option>
<option value="consumable">소모품</option>
<option value="safety">안전용품</option>
<option value="repair">수선비</option>
<option value="equipment">설비</option>
</select>
<button onclick="loadRequests()" class="px-3 py-2 border rounded-lg text-sm text-gray-600 hover:bg-gray-100">
<i class="fas fa-sync-alt"></i>
</button>
<!-- 그룹 액션 (admin only) -->
<div id="batchActions" class="hidden ml-auto flex items-center gap-2">
<span id="selectedCount" class="text-sm text-gray-500">0건 선택</span>
<button onclick="openBatchCreateModal()" class="px-3 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
<i class="fas fa-layer-group mr-1"></i>그룹 생성
</button>
</div>
<button id="batchViewBtn" class="hidden ml-2 px-3 py-2 border border-blue-300 text-blue-600 rounded-lg text-sm hover:bg-blue-50" onclick="toggleBatchView()">
<i class="fas fa-layer-group mr-1"></i>그룹 보기
</button>
</div>
<!-- 그룹 목록 (토글) -->
<div id="batchView" class="hidden mb-4">
<div class="bg-white rounded-xl shadow-sm p-4">
<div class="flex justify-between items-center mb-3">
<h3 class="font-semibold text-gray-700"><i class="fas fa-layer-group mr-1 text-blue-500"></i>구매 그룹</h3>
<button onclick="loadBatches()" class="text-xs text-gray-400 hover:text-gray-600"><i class="fas fa-sync-alt"></i></button>
</div>
<div id="batchList" class="space-y-2 text-sm"></div>
</div>
</div>
<!-- 신청 목록 -->
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-gray-600 text-xs uppercase">
<tr>
<th class="px-2 py-3 text-center" id="thCheckbox" style="display:none"><input type="checkbox" id="selectAllCb" onchange="toggleSelectAll(this)"></th>
<th class="px-4 py-3 text-left">품목</th>
<th class="px-4 py-3 text-left">분류</th>
<th class="px-4 py-3 text-right">수량</th>
<th class="px-4 py-3 text-left">신청자</th>
<th class="px-4 py-3 text-left">신청일</th>
<th class="px-4 py-3 text-center">상태</th>
<th class="px-4 py-3 text-center">액션</th>
</tr>
</thead>
<tbody id="prRequestList" class="divide-y">
<tr><td colspan="7" class="px-4 py-8 text-center text-gray-400">데이터를 불러오는 중...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- 구매 처리 모달 -->
<div id="purchaseModal" class="hidden fixed inset-0 bg-black/40 z-50 flex items-center justify-center p-4" onclick="if(event.target===this)closePurchaseModal()">
<div class="bg-white rounded-xl shadow-xl max-w-lg w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">구매 처리</h3>
<button onclick="closePurchaseModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
</div>
<div id="purchaseModalInfo" class="bg-gray-50 rounded-lg p-3 mb-4 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="pmVendor" class="w-full px-3 py-2 border rounded-lg text-sm">
<option value="">업체 선택</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">구매일 <span class="text-red-400">*</span></label>
<input type="date" id="pmDate" class="w-full px-3 py-2 border rounded-lg text-sm">
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">실구매 단가 <span class="text-red-400">*</span></label>
<input type="number" id="pmUnitPrice" class="w-full px-3 py-2 border rounded-lg text-sm" min="0" oninput="showPriceDiff()">
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">수량</label>
<input type="number" id="pmQuantity" class="w-full px-3 py-2 border rounded-lg text-sm" min="1" value="1">
</div>
<div class="col-span-2" id="pmPriceDiffArea">
</div>
<!-- 마스터 등록 체크박스 (미등록 품목일 때만 표시) -->
<div class="col-span-2 hidden" id="pmMasterRegisterWrap">
<label class="flex items-center gap-2 cursor-pointer p-2 bg-orange-50 rounded-lg">
<input type="checkbox" id="pmRegisterToMaster" checked class="h-4 w-4 rounded text-orange-600">
<span class="text-sm text-gray-700">소모품 마스터에 등록</span>
<span class="text-xs text-gray-400">(구매 처리 시 자동 등록)</span>
</label>
</div>
<div class="col-span-2">
<label class="block text-xs font-medium text-gray-600 mb-1">메모</label>
<input type="text" id="pmNotes" class="w-full px-3 py-2 border rounded-lg text-sm">
</div>
</div>
<div class="flex justify-end mt-4 gap-2">
<button type="button" onclick="closePurchaseModal()" class="px-4 py-2 border rounded-lg text-sm hover:bg-gray-50">취소</button>
<button type="button" onclick="submitPurchase()" class="px-4 py-2 bg-orange-600 text-white rounded-lg text-sm hover:bg-orange-700">구매 완료</button>
</div>
</div>
</div>
<!-- 보류 모달 -->
<div id="holdModal" class="hidden fixed inset-0 bg-black/40 z-50 flex items-center justify-center p-4" onclick="if(event.target===this)closeHoldModal()">
<div class="bg-white rounded-xl shadow-xl max-w-md w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">보류 처리</h3>
<button onclick="closeHoldModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">보류 사유</label>
<textarea id="holdReason" rows="3" class="w-full px-3 py-2 border rounded-lg text-sm" placeholder="보류 사유를 입력하세요"></textarea>
</div>
<div class="flex justify-end mt-4 gap-2">
<button type="button" onclick="closeHoldModal()" class="px-4 py-2 border rounded-lg text-sm hover:bg-gray-50">취소</button>
<button type="button" onclick="submitHold()" class="px-4 py-2 bg-gray-600 text-white rounded-lg text-sm hover:bg-gray-700">보류</button>
</div>
</div>
</div>
<!-- 그룹 생성 모달 -->
<div id="batchCreateModal" class="hidden fixed inset-0 bg-black/40 z-50 flex items-center justify-center p-4" onclick="if(event.target===this)closeBatchCreateModal()">
<div class="bg-white rounded-xl shadow-xl max-w-md w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-blue-700"><i class="fas fa-layer-group mr-2"></i>구매 그룹 <20><><EFBFBD></h3>
<button onclick="closeBatchCreateModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
</div>
<div id="batchSelectedInfo" class="mb-3 text-sm text-gray-600"></div>
<div class="mb-3">
<label class="block text-xs font-medium text-gray-600 mb-1">그룹명</label>
<input type="text" id="bcName" class="w-full px-3 py-2 border rounded-lg text-sm" placeholder="예: 4월 소모품 1차">
</div>
<div class="grid grid-cols-2 gap-3 mb-3">
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">분류</label>
<select id="bcCategory" class="w-full px-3 py-2 border rounded-lg text-sm">
<option value="">자동 분류</option>
<option value="consumable">소모품</option>
<option value="safety">안전<EFBFBD><EFBFBD><EFBFBD></option>
<option value="repair">수선비</option>
<option value="equipment">설비</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">업체</label>
<select id="bcVendor" class="w-full px-3 py-2 border rounded-lg text-sm"></select>
</div>
</div>
<div class="mb-3">
<label class="block text-xs font-medium text-gray-600 mb-1">메모</label>
<input type="text" id="bcNotes" class="w-full px-3 py-2 border rounded-lg text-sm" placeholder="메모">
</div>
<div class="flex justify-end gap-2">
<button type="button" onclick="closeBatchCreateModal()" class="px-4 py-2 border rounded-lg text-sm hover:bg-gray-50">취소</button>
<button type="button" onclick="submitBatchCreate()" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">생성</button>
</div>
</div>
</div>
<!-- 입고 처리 <20><>-->
<div id="receiveModal" class="hidden fixed inset-0 bg-black/40 z-50 flex items-center justify-center p-4" onclick="if(event.target===this)closeReceiveModal()">
<div class="bg-white rounded-xl shadow-xl max-w-md w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-teal-700"><i class="fas fa-box-open mr-2"></i>입고 처리</h3>
<button onclick="closeReceiveModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
</div>
<div id="receiveModalInfo" class="mb-4 p-3 bg-gray-50 rounded-lg"></div>
<div class="mb-3">
<label class="block text-xs font-medium text-gray-600 mb-1">보관 위치</label>
<input type="text" id="rcLocation" class="w-full px-3 py-2 border rounded-lg text-sm" placeholder="예: 1층 자재창고 A-3선반">
</div>
<div class="mb-3">
<label class="block text-xs font-medium text-gray-600 mb-1">입고 사진 (선택)</label>
<input type="file" id="rcPhotoInput" accept="image/*,.heic,.heif" onchange="onReceivePhotoSelected(this)" class="text-sm">
<div id="rcPhotoPreview" class="hidden mt-2">
<img id="rcPhotoPreviewImg" class="w-24 h-24 rounded object-cover">
</div>
</div>
<div class="flex justify-end mt-4 gap-2">
<button type="button" onclick="closeReceiveModal()" class="px-4 py-2 border rounded-lg text-sm hover:bg-gray-50">취소</button>
<button type="button" onclick="submitReceive()" class="px-4 py-2 bg-teal-600 text-white rounded-lg text-sm hover:bg-teal-700">입고 확인</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/static/js/tkfb-core.js?v=2026040102"></script>
<script src="/static/js/purchase-request.js?v=2026040102"></script>
</body>
</html>