sungro815 b0545ab5b9 관리자 상품관리 완료
웹사이트 상품리스트 상세보기 작업중
2026-02-20 18:11:03 +09:00

204 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('admin.layouts.app')
@section('title', '핀(PIN) 재고 관리')
@section('page_title', '핀(PIN) 재고 관리')
@push('head')
<style>
.info-box { background: rgba(59,130,246,.1); border: 1px solid rgba(59,130,246,.3); padding: 16px; border-radius: 8px; margin-bottom: 20px; }
.info-box ul { margin: 8px 0 0 20px; padding: 0; color: #9ca3af; font-size: 13px; line-height: 1.6; }
.p-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.p-table th { text-align: left; padding: 12px; border-bottom: 1px solid rgba(255,255,255,.1); color: #9ca3af; font-weight: 600; white-space: nowrap; }
.p-table td { padding: 12px; border-bottom: 1px solid rgba(255,255,255,.05); vertical-align: middle; }
.p-table tr:hover td { background: rgba(255,255,255,.02); }
.search-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; align-items: end; margin-bottom: 20px; }
.pin-code-text { font-family: monospace; font-size: 14px; letter-spacing: 1px; color: #e5e7eb; background: rgba(0,0,0,.3); padding: 4px 8px; border-radius: 4px; }
/* 대시보드 스타일 */
.stat-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 24px; }
.stat-box { background: rgba(0,0,0,.2); border: 1px solid rgba(255,255,255,.1); padding: 16px; border-radius: 8px; text-align: center; }
.stat-label { font-size: 12px; color: #9ca3af; margin-bottom: 8px; }
.stat-value { font-size: 24px; font-weight: bold; }
</style>
@endpush
@section('content')
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<div>
<span style="color: #9ca3af; font-size: 13px;">상품명:</span> <strong style="color: #fff; font-size: 16px; margin-right: 12px;">{{ $product->name }}</strong>
<span style="color: #9ca3af; font-size: 13px;">권종명:</span> <strong style="color: #60a5fa; font-size: 16px;">{{ $sku->name }}</strong>
</div>
<a href="{{ route('admin.products.edit', $product->id) }}" class="lbtn lbtn--ghost"> 상품 수정으로 돌아가기</a>
</div>
<div class="stat-grid">
<div class="stat-box" style="border-top: 3px solid #6b7280;">
<div class="stat-label"> 누적 </div>
<div class="stat-value" style="color: #fff;">{{ number_format($stats['total']) }}</div>
</div>
<div class="stat-box" style="border-top: 3px solid #34d399;">
<div class="stat-label">판매 대기 (AVAILABLE)</div>
<div class="stat-value" style="color: #34d399;">{{ number_format($stats['AVAILABLE']) }}</div>
</div>
<div class="stat-box" style="border-top: 3px solid #60a5fa;">
<div class="stat-label">판매 완료 (SOLD)</div>
<div class="stat-value" style="color: #60a5fa;">{{ number_format($stats['SOLD']) }}</div>
</div>
<div class="stat-box" style="border-top: 3px solid #f87171;">
<div class="stat-label">회수됨 (RECALLED)</div>
<div class="stat-value" style="color: #f87171;">{{ number_format($stats['RECALLED']) }}</div>
</div>
<div class="stat-box" style="border-top: 3px solid #9ca3af;">
<div class="stat-label">기타 (결제중/만료)</div>
<div class="stat-value" style="color: #9ca3af;">{{ number_format($stats['HOLD'] + $stats['EXPIRED']) }}</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 30px;">
<div class="a-card" style="padding: 24px; border-top: 3px solid #10b981;">
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: #fff;"> 대량 등록 (붙여넣기)</h3>
<form action="{{ route('admin.pins.storeBulk', ['productId' => $product->id, 'skuId' => $sku->id]) }}" method="POST">
@csrf
<div class="a-field">
<textarea name="bulk_text" class="a-input" rows="5" placeholder="핀번호, 정액, 원가 (1줄 1개)&#10;ABCD-1234, 10000, 9000" required></textarea>
</div>
<div style="display: flex; gap: 10px; align-items: flex-end;">
<div class="a-field" style="margin: 0; flex: 1;">
<label class="a-label">일괄 유효기간 (선택)</label>
<input type="date" name="expiry_date" class="a-input">
</div>
<button type="submit" class="lbtn lbtn--primary" onclick="return confirm('등록하시겠습니까?');">등록 실행</button>
</div>
</form>
</div>
<div class="a-card" style="padding: 24px; border-top: 3px solid #f43f5e;">
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: #fff;">📤 최신 회수 추출 (ZIP)</h3>
<div style="font-size: 12px; color: #9ca3af; margin-bottom: 12px;"> 등록된 역순(가장 최신 )으로 '판매 대기' 핀만 회수합니다.</div>
<form action="{{ route('admin.pins.recallBulk', ['productId' => $product->id, 'skuId' => $sku->id]) }}" method="POST">
@csrf
<div style="display: flex; gap: 10px; margin-bottom: 12px;">
<div class="a-field" style="margin:0; width: 150px;">
<label class="a-label">회수 수량</label>
<select name="amount_type" class="a-input" id="recall_amount_type" onchange="toggleCustomAmount()">
<option value="10">10 회수</option>
<option value="50">50 회수</option>
<option value="100">100 회수</option>
<option value="ALL">전체 회수 ({{ number_format($stats['AVAILABLE']) }})</option>
<option value="CUSTOM">직접 입력</option>
</select>
</div>
<div class="a-field" style="margin:0; width: 100px; display: none;" id="custom_amount_wrap">
<label class="a-label">직접 입력</label>
<input type="number" name="custom_amount" id="custom_amount_input" class="a-input" placeholder="수량">
</div>
</div>
<div style="display: flex; gap: 10px; align-items: flex-end;">
<div class="a-field" style="margin:0; flex: 1;">
<label class="a-label">압축 비밀번호 설정 (필수, 최소 4)</label>
<input type="text" name="zip_password" class="a-input" placeholder="비밀번호" required minlength="4">
</div>
<button type="submit" class="lbtn lbtn--danger" onclick="return confirm('⚠️ 선택한 수량의 핀이 회수(RECALLED) 처리되며, CSV 압축 파일로 다운로드됩니다.\n계속하시겠습니까?');">회수 다운로드</button>
</div>
</form>
</div>
</div>
<div class="a-card">
<div style="padding: 16px; border-bottom: 1px solid rgba(255,255,255,.05);">
<h3 style="margin: 0; font-size: 16px; color: #fff;">📋 재고 목록 검색</h3>
</div>
<div style="padding: 16px;">
<form method="GET" action="{{ route('admin.pins.index', ['productId' => $product->id, 'skuId' => $sku->id]) }}">
<div class="search-grid" style="grid-template-columns: 200px 300px auto;">
<div class="a-field" style="margin:0;">
<label class="a-label">상태 필터</label>
<select class="a-input" name="status" onchange="this.form.submit()">
<option value="">-- 전체 --</option>
<option value="AVAILABLE" {{ request('status') === 'AVAILABLE' ? 'selected' : '' }}>판매 대기</option>
<option value="SOLD" {{ request('status') === 'SOLD' ? 'selected' : '' }}>판매 완료</option>
<option value="RECALLED" {{ request('status') === 'RECALLED' ? 'selected' : '' }}>회수됨</option>
</select>
</div>
<div class="a-field" style="margin:0;">
<label class="a-label"> 번호 검색 (정확히 일치)</label>
<div style="display:flex; gap:8px;">
<input type="text" class="a-input" name="q" value="{{ request('q') }}" placeholder="전체 핀 번호 입력">
<button type="submit" class="lbtn lbtn--primary">검색</button>
</div>
</div>
<div>
@if(request('q') || request('status'))
<a href="{{ route('admin.pins.index', ['productId' => $product->id, 'skuId' => $sku->id]) }}" class="lbtn lbtn--ghost" style="margin-bottom: 2px;">초기화</a>
@endif
</div>
</div>
</form>
<div style="overflow-x: auto;">
<table class="p-table">
<thead>
<tr>
<th style="width: 60px; text-align: center;">ID</th>
<th>(PIN) 번호 (마스킹)</th>
<th style="text-align: right;">정액 금액</th>
<th style="text-align: right;">매입 원가</th>
<th style="text-align: right;">마진율</th>
<th style="text-align: center;">상태</th>
<th style="text-align: center;">등록일시</th>
</tr>
</thead>
<tbody>
@forelse($pins as $pin)
<tr>
<td style="text-align: center; color: #6b7280;">{{ $pin->id }}</td>
<td><span class="pin-code-text">{{ $pin->decrypted_code }}</span></td>
<td style="text-align: right; color: #fff;">{{ number_format($pin->face_value) }}</td>
<td style="text-align: right; color: #f87171;">{{ number_format($pin->buy_price) }}</td>
<td style="text-align: right; color: #34d399;">{{ $pin->margin_rate }}%</td>
<td style="text-align: center;">
@if($pin->status === 'AVAILABLE') <span class="pill pill--ok">판매대기</span>
@elseif($pin->status === 'SOLD') <span class="pill pill--muted">판매완료</span>
@elseif($pin->status === 'RECALLED') <span class="pill pill--danger">회수됨</span>
@else <span class="pill pill--warning">{{ $pin->status }}</span> @endif
</td>
<td style="text-align: center; color: #9ca3af;">{{ \Carbon\Carbon::parse($pin->created_at)->format('Y-m-d H:i') }}</td>
</tr>
@empty
<tr><td colspan="7" style="text-align: center; padding: 40px; color: #9ca3af;">등록된 번호가 없습니다.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div style="margin-top:12px;">
{{ $pins->onEachSide(1)->links('vendor.pagination.admin') }}
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
function toggleCustomAmount() {
const typeSelect = document.getElementById('recall_amount_type');
const customWrap = document.getElementById('custom_amount_wrap');
const customInput = document.getElementById('custom_amount_input');
if (typeSelect.value === 'CUSTOM') {
customWrap.style.display = 'block';
customInput.setAttribute('required', 'required');
} else {
customWrap.style.display = 'none';
customInput.removeAttribute('required');
}
}
</script>
@endpush