369 lines
18 KiB
PHP

@extends('web.layouts.subpage')
@push('styles')
<style>
/* 전체 레이아웃 50:50 비율 */
.p-detail-container { display: flex; gap: 40px; margin-bottom: 40px; align-items: flex-start; }
.p-detail-visual { flex: 5; }
.p-detail-info { flex: 5; display: flex; flex-direction: column; gap: 24px; }
/* 이미지 카드 */
.p-main-img-card { background: #fff; border: 1px solid #eee; border-radius: 16px; display: flex; align-items: center; justify-content: center; padding: 30px; aspect-ratio: 1/1; margin-bottom: 30px; }
.p-main-img-card img { width: 100%; max-width: 320px; height: auto; filter: drop-shadow(0 10px 20px rgba(0,0,0,0.05)); }
/* 탭 버튼 그룹 (부트스트랩 스타일) */
.p-tab-container { margin-top: 20px; }
.p-btn-group { display: flex; border: 1px solid #dee2e6; border-radius: 8px; overflow: hidden; margin-bottom: 20px; }
.p-tab-btn { flex: 1; padding: 12px; border: none; background: #f8f9fa; color: #495057; font-weight: 700; cursor: pointer; transition: 0.2s; border-right: 1px solid #dee2e6; }
.p-tab-btn:last-child { border-right: none; }
.p-tab-btn.is-active { background: #3182ce; color: #fff; }
.p-tab-content { padding: 20px; background: #fff; border: 1px solid #edf2f7; border-radius: 12px; min-height: 150px; }
.p-content-pane { display: none; font-size: 15px; color: #4a5568; line-height: 1.7; }
.p-content-pane.is-active { display: block; }
/* 권종 선택 (웹: 2줄) */
.p-sku-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.p-sku-btn { background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 16px; transition: all 0.2s; position: relative; cursor: pointer; text-align: left; }
.p-sku-btn.is-active { border-color: #3182ce; background: #ebf8ff; box-shadow: 0 0 0 1px #3182ce; }
.p-sku-price { font-size: 18px; font-weight: 800; color: #2d3748; }
/* 수량 및 결제 수단 */
.p-qty-wrapper { display: flex; align-items: center; justify-content: space-between; padding: 15px 0; border-top: 1px solid #edf2f7; border-bottom: 1px solid #edf2f7; }
.p-qty-ctrl { display: flex; align-items: center; border: 1px solid #cbd5e0; border-radius: 8px; overflow: hidden; background: #fff; }
.p-qty-btn { width: 44px; height: 44px; border: none; background: #f7fafc; font-size: 20px; cursor: pointer; }
.p-qty-input { width: 60px; height: 44px; border: none; text-align: center; font-weight: 700; font-size: 16px; }
.p-pay-method-grid { display: flex; gap: 8px; flex-wrap: wrap; }
.p-pay-btn { font-size: 12px; background: #fff; border: 1px solid #edf2f7; padding: 8px 14px; border-radius: 8px; color: #4a5568; cursor: pointer; }
.p-pay-btn.is-active { background: #3182ce; color: #fff; border-color: #3182ce; font-weight: 700; }
/* 요약 영역 */
.p-price-summary { margin-top: 30px; padding: 20px; background: #f8fafc; border-radius: 12px; }
.p-total-price { font-size: 28px; font-weight: 900; color: #2b6cb0; }
.p-btn-order-submit { background: #3182ce; color: #fff; width: 100%; height: 60px; border-radius: 12px; font-weight: 700; border: none; font-size: 18px; cursor: pointer; }
/* 모바일 대응 */
@media (max-width: 991px) {
.p-detail-container { flex-direction: column; }
/* 모바일 권종 한 줄 배치 */
.p-sku-grid { grid-template-columns: 1fr; }
/* 모바일 탭 영역 하단 배치용 순서 변경 */
.p-detail-visual { display: flex; flex-direction: column; width: 100%; }
.p-tab-container { order: 10; margin-top: 40px; } /* 결제정보 아래로 이동 */
.p-detail-info { width: 100%; }
}
</style>
@endpush
@section('subcontent')
<div class="p-detail-container">
{{-- 왼쪽 영역 --}}
<div class="p-detail-visual">
<div class="p-main-img-card">
<img src="{{ $product->thumb_path ?? '/assets/images/no-image.png' }}" id="productImg">
</div>
{{-- 상세 정보 그룹 --}}
<div class="p-tab-container">
<div class="p-btn-group">
<button type="button" class="p-tab-btn is-active" onclick="window.switchTab(this, 'desc')">상품설명</button>
<button type="button" class="p-tab-btn" onclick="window.switchTab(this, 'guide')">이용안내</button>
<button type="button" class="p-tab-btn" onclick="window.switchTab(this, 'warning')">주의사항</button>
</div>
<div class="p-tab-content">
<div id="pane-desc" class="p-content-pane is-active">{!! $product->description !!}</div>
<div id="pane-guide" class="p-content-pane">{!! $product->guide !!}</div>
<div id="pane-warning" class="p-content-pane">{!! $product->warning !!}</div>
</div>
</div>
</div>
{{-- 오른쪽 영역 --}}
<div class="p-detail-info">
<div class="p-info-header">
<span style="color:#3182ce; font-weight:700; font-size:14px; letter-spacing:1px;">VERIFIED RESELLER</span>
<h2>{{ $product->name }}</h2>
</div>
<div class="p-order-form">
<div class="p-sku-grid">
@foreach($skus as $sku)
<div class="p-sku-btn @if($loop->first) is-active @endif"
data-sku-id="{{ $sku->id }}"
data-price="{{ $sku->final_price }}"
onclick="window.handleSkuSelect(this)">
@if($sku->discount_value > 0)
<span class="p-sku-badge">{{ $sku->discount_type === 'PERCENT' ? $sku->discount_value.'%' : number_format($sku->discount_value).'원' }}</span>
@endif
<span style="display:block; font-size:13px; color:#a0aec0; margin-bottom:4px;">{{ $sku->name }}</span>
<div style="display:flex; align-items:baseline; gap:8px;">
<span class="p-sku-price">{{ number_format($sku->final_price) }}</span>
@if($sku->discount_value > 0)
<span style="font-size:13px; color:#cbd5e0; text-decoration:line-through;">{{ number_format($sku->face_value) }}</span>
@endif
</div>
</div>
@endforeach
</div>
<div class="p-qty-wrapper" style="margin-top:25px;">
<label class="p-option-title" style="margin-bottom:0;">구매 수량</label>
<div class="p-qty-ctrl">
<button type="button" class="p-qty-btn" onclick="window.changeQty(-1)">-</button>
<input type="text" id="pOrderQty" class="p-qty-input" value="{{ $product->min_buy_qty }}" readonly>
<button type="button" class="p-qty-btn" onclick="window.changeQty(1)">+</button>
</div>
</div>
<div style="margin-top:20px; padding:15px; background:#f7fafc; border-radius:12px;">
<span style="font-size:13px; font-weight:700; color:#4a5568; display:block; margin-bottom:10px;">결제 수단 선택</span>
<div class="p-pay-method-grid">
@foreach($payments as $pay)
<button type="button"
class="p-pay-btn @if($loop->first) is-active @endif"
data-pay-id="{{ $pay->id }}"
data-fee-rate="{{ (float)$pay->customer_fee_rate }}"
onclick="window.handlePaySelect(this)">
{{ $pay->display_name }}
</button>
@endforeach
</div>
</div>
<div class="p-price-summary">
<div class="p-summary-row" style="display:flex; justify-content:space-between; font-size:14px; color:#718096; margin-bottom:8px;">
<span>상품 합계</span>
<span><span id="pBaseTotal">0</span></span>
</div>
<div class="p-summary-row" style="display:flex; justify-content:space-between; font-size:14px; color:#718096; margin-bottom:8px;">
<span>결제 수수료 (<span id="pFeeRateDisplay">0</span>%)</span>
<span>+ <span id="pFeeAmount">0</span></span>
</div>
<div class="p-total-row" style="display:flex; justify-content:space-between; align-items:center; margin-top:10px; padding-top:10px; border-top:1px solid #e2e8f0;">
<span style="font-weight:800; color:#4a5568; font-size:16px;">최종 결제금액</span>
<div class="p-total-price"><span id="pTotalVal">0</span><small style="font-size:18px; margin-left:2px;"></small></div>
</div>
</div>
<div style="margin-top:25px;">
<button type="button" class="p-btn-order-submit" onclick="window.submitOrder()">구매하기</button>
</div>
</div>
</div>
</div>
<div id="payModal" style="display:none; position:fixed; inset:0; z-index:9999; background:rgba(0,0,0,.6);">
<div id="payModalBox" style="
position:absolute;
left:50%; top:50%;
transform:translate(-50%,-50%);
width:min(520px, 92vw);
height:min(860px, 92vh);
background:#fff;
border-radius:14px;
overflow:hidden;">
<div style="display:flex; justify-content:space-between; align-items:center; padding:10px 12px; border-bottom:1px solid #eee;">
<strong>
결제 진행
<span id="payModeBadge" style="display:none; margin-left:8px; padding:2px 8px; border-radius:999px; font-size:12px; color:#fff; background:#d32f2f;">
DEV(자동취소)
</span>
</strong>
<button type="button" onclick="window.onPayModalCloseClick()" style="border:0;background:#fff;font-size:18px;cursor:pointer;"></button>
</div>
<iframe id="payFrame" style="width:100%; height:calc(100% - 44px); border:0;"></iframe>
</div>
</div>
@endsection
@push('scripts')
<script>
const PAY_MODAL_SIZE = {
card: { w: 750, h: 550 },
vact: { w: 550, h: 550 },
phone: { w: 420, h: 760 },
};
// pay_id -> type 매핑(네 DB 기준)
function payTypeFromPayId(payId) {
const id = parseInt(payId, 10);
if (id === 1) return 'phone'; // MOBILE
if (id === 2) return 'card'; // CREDIT_CARD(일반)
if (id === 7) return 'card'; // CREDIT_CARD_REFUND(환금) - 사이즈는 카드와 동일로 시작
if (id === 3) return 'vact'; // VACT
return 'card';
}
function setPayModalSize(type) {
const box = document.getElementById('payModalBox');
const s = PAY_MODAL_SIZE[type] || PAY_MODAL_SIZE.card;
// 화면에 맞게 제한
box.style.width = `min(${s.w}px, 92vw)`;
box.style.height = `min(${s.h}px, 92vh)`;
}
window.__payCtx = null;
window.setPayCtx = function(ctx){
window.__payCtx = ctx || null;
updatePayBadge(window.__payCtx);
};
window.openPayModal = function(url, payId){
updatePayBadge(null);
const m = document.getElementById('payModal');
const f = document.getElementById('payFrame');
const type = payTypeFromPayId(payId);
setPayModalSize(type);
f.src = url;
m.style.display = 'block';
};
window.closePayModal = function(opts = {}){
const m = document.getElementById('payModal');
const f = document.getElementById('payFrame');
m.style.display = 'none';
if (!opts.keepFrame) {
f.src = 'about:blank';
}
};
window.onPayModalCloseClick = async function(){
const ctx = window.__payCtx;
try{
if (ctx && ctx.token) {
const r = await fetch('/pay/danal/cancel?a=' + encodeURIComponent(ctx.token), {
method:'POST',
credentials:'same-origin',
});
//console.log('cancel status', r.status);
}
} catch(e){}
window.closePayModal();
await window.showMsg('결제가 취소되었습니다.', { type:'alert', title:'결제취소' });
};
function updatePayBadge(ctx){
const badge = document.getElementById('payModeBadge');
if (!badge) return;
// 기본 숨김
badge.style.display = 'none';
// 휴대폰 + dev 일 때만 표시
if (ctx && ctx.method === 'phone' && ctx.phone_mode === 'dev') {
badge.textContent = 'DEV';
badge.style.display = 'inline-block';
}
}
(function() {
const POLICY = {
minQty: parseInt("{{ $product->min_buy_qty }}") || 1,
maxQty: parseInt("{{ $product->max_buy_qty }}") || 999,
maxAmount: parseInt("{{ $product->max_buy_amount }}") || 0
};
let currentUnitPrice = 0;
let currentFeeRate = 0;
// 탭 전환 함수
window.switchTab = function(btn, type) {
document.querySelectorAll('.p-tab-btn').forEach(b => b.classList.remove('is-active'));
btn.classList.add('is-active');
document.querySelectorAll('.p-content-pane').forEach(p => p.classList.remove('is-active'));
document.getElementById('pane-' + type).classList.add('is-active');
};
window.handlePaySelect = function(el) {
document.querySelectorAll('.p-pay-btn').forEach(b => b.classList.remove('is-active'));
el.classList.add('is-active');
currentFeeRate = parseFloat(el.dataset.feeRate);
calculateTotal();
};
window.handleSkuSelect = function(el) {
document.querySelectorAll('.p-sku-btn').forEach(b => b.classList.remove('is-active'));
el.classList.add('is-active');
currentUnitPrice = parseInt(el.dataset.price);
validateAndCalculate();
};
window.changeQty = async function(delta) {
const qtyInput = document.getElementById('pOrderQty');
let nextQty = parseInt(qtyInput.value) + delta;
if (nextQty < POLICY.minQty) return;
if (POLICY.maxQty > 0 && nextQty > POLICY.maxQty) {
await showMsg(
`선택하신 수량이 1회 최대 구매 수량(${POLICY.maxQty}개)을 초과합니다.\n수량을 줄여 다시 시도해 주세요.`,
{ type:'alert', title:'구매 수량 안내' }
);
return;
}
const money = (n) => Number(n || 0).toLocaleString('ko-KR');
if (POLICY.maxAmount > 0 && (currentUnitPrice * nextQty) > POLICY.maxAmount) {
await showMsg(
`선택하신 수량으로 결제하면 1회 결제 한도(${money(POLICY.maxAmount)}원)를 초과합니다.\n수량을 줄여 다시 시도해 주세요.`,
{ type:'alert', title:'결제 한도 안내' }
);
return;
}
qtyInput.value = nextQty;
calculateTotal();
};
function validateAndCalculate() {
const qtyInput = document.getElementById('pOrderQty');
let qty = parseInt(qtyInput.value);
if (POLICY.maxAmount > 0 && (currentUnitPrice * qty) > POLICY.maxAmount) {
qty = Math.floor(POLICY.maxAmount / currentUnitPrice);
if(qty < POLICY.minQty) qty = POLICY.minQty;
qtyInput.value = qty;
}
calculateTotal();
}
function calculateTotal() {
const qty = parseInt(document.getElementById('pOrderQty').value);
const baseTotal = currentUnitPrice * qty;
const feeAmount = Math.ceil(baseTotal * (currentFeeRate / 100));
document.getElementById('pBaseTotal').innerText = baseTotal.toLocaleString();
document.getElementById('pFeeRateDisplay').innerText = currentFeeRate;
document.getElementById('pFeeAmount').innerText = feeAmount.toLocaleString();
document.getElementById('pTotalVal').innerText = (baseTotal + feeAmount).toLocaleString();
}
window.submitOrder = async function() {
const activeSku = document.querySelector('.p-sku-btn.is-active');
const activePay = document.querySelector('.p-pay-btn.is-active');
const qty = document.getElementById('pOrderQty').value;
if (!activeSku) return await showMsg('권종을 선택해주세요.', { type:'alert', title:'권종선택' });
const url = "/order/checkout?sku_id=" + activeSku.dataset.skuId + "&qty=" + qty + "&pay_id=" + activePay.dataset.payId;
window.openPayModal(url);
};
document.addEventListener('DOMContentLoaded', function() {
const firstSku = document.querySelector('.p-sku-btn.is-active');
const firstPay = document.querySelector('.p-pay-btn.is-active');
if (firstSku) currentUnitPrice = parseInt(firstSku.dataset.price);
if (firstPay) currentFeeRate = parseFloat(firstPay.dataset.feeRate);
calculateTotal();
});
})();
</script>
@endpush