feat(purchase): 구매신청 검색/직접입력/사진첨부/HEIC 지원/마스터 자동등록

- 소모품 select → 검색형 드롭다운 (debounce + 키보드 탐색)
- 미등록 품목 직접 입력 + 분류 선택 지원
- 사진 첨부 (base64 업로드, HEIC→JPEG 프론트 변환)
- 구매 처리 시 미등록 품목 소모품 마스터 자동 등록
- item_id NULL 허용, LEFT JOIN, custom_item_name/custom_category/photo_path 컬럼
- DB 마이그레이션 필요: ALTER TABLE purchase_requests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-13 21:49:41 +09:00
parent 13e177e818
commit cae735f243
8 changed files with 414 additions and 69 deletions

View File

@@ -7,6 +7,18 @@
<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">
<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">
@@ -41,12 +53,13 @@
<!-- 구매신청 폼 -->
<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-end">
<div class="sm:col-span-2">
<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>
<select id="prItemSelect" class="w-full px-3 py-2 border rounded-lg text-sm focus:ring-2 focus:ring-orange-300" onchange="onItemSelect()">
<option value="">소모품 선택</option>
</select>
<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>
@@ -54,6 +67,16 @@
<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>
@@ -64,6 +87,21 @@
<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>구매신청
@@ -140,7 +178,15 @@
<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" class="hidden">
<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>
@@ -175,7 +221,7 @@
</div>
</div>
</div>
<script src="/static/js/tkfb-core.js?v=20260313"></script>
<script src="/static/js/purchase-request.js?v=20260313"></script>
<script src="/static/js/tkfb-core.js?v=20260313b"></script>
<script src="/static/js/purchase-request.js?v=20260313b"></script>
</body>
</html>