204 lines
12 KiB
PHP
204 lines
12 KiB
PHP
@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개) 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
|