336 lines
15 KiB
PHP
336 lines
15 KiB
PHP
@extends('web.layouts.subpage')
|
|
|
|
@php
|
|
$mypageActive = $mypageActive ?? 'usage';
|
|
$filters = $filters ?? ['q'=>'','method'=>'','status'=>'','from'=>'','to'=>''];
|
|
|
|
$listQuery = request()->only(['q', 'method', 'status', 'from', 'to', 'page']);
|
|
$listQuery = array_filter($listQuery, fn($v) => $v !== null && $v !== '');
|
|
|
|
$methodLabel = function ($m) {
|
|
$m = (string)$m;
|
|
return match ($m) {
|
|
'card' => '카드',
|
|
'phone' => '휴대폰',
|
|
'wire' => '계좌이체',
|
|
'vact' => '가상계좌',
|
|
default => $m ?: '-',
|
|
};
|
|
};
|
|
|
|
// 상태는 "결제완료/결제취소" 중심 + 화면 깨짐 방지 최소 처리
|
|
$payStatusLabel = function ($r) {
|
|
$aCancel = (string)($r->attempt_cancel_status ?? 'none');
|
|
$oCancel = (string)($r->order_cancel_status ?? 'none');
|
|
|
|
if ($aCancel === 'success' || $oCancel === 'success') return '결제취소';
|
|
|
|
$aStatus = (string)($r->attempt_status ?? '');
|
|
$oPay = (string)($r->order_stat_pay ?? '');
|
|
|
|
if ($aStatus === 'paid' || $oPay === 'p') return '결제완료';
|
|
if ($aStatus === 'failed' || $oPay === 'f') return '결제실패';
|
|
if ($aStatus === 'issued' || $oPay === 'w') return '입금대기';
|
|
|
|
return '진행중';
|
|
};
|
|
|
|
$issueStatusLabel = function ($r) use ($payStatusLabel) {
|
|
$pay = $payStatusLabel($r);
|
|
|
|
// 결제취소면 발행 상태는 의미 없음
|
|
if ($pay === '결제취소') return '-';
|
|
|
|
$issuedCount = (int)($r->issued_count ?? 0);
|
|
|
|
return $issuedCount > 0 ? '발행완료' : '발행대기';
|
|
};
|
|
|
|
$payStatusClass = function ($label) {
|
|
return match ($label) {
|
|
'결제취소' => 'pill--danger',
|
|
'결제완료' => 'pill--ok',
|
|
'결제실패' => 'pill--danger',
|
|
'입금대기' => 'pill--wait',
|
|
default => 'pill--muted',
|
|
};
|
|
};
|
|
|
|
$issueStatusClass = function ($label) {
|
|
return match ($label) {
|
|
'발행완료' => 'pill--ok',
|
|
'발행대기' => 'pill--wait',
|
|
default => 'pill--muted',
|
|
};
|
|
};
|
|
|
|
$formatDate = function ($v) {
|
|
$s = (string)$v;
|
|
if ($s === '') return '-';
|
|
try {
|
|
return \Carbon\Carbon::parse($s)->format('Y-m-d H:i'); // 분까지
|
|
} catch (\Throwable $e) {
|
|
return mb_substr($s, 0, 16);
|
|
}
|
|
};
|
|
@endphp
|
|
|
|
@section('title', $pageTitle ?? '구매내역d')
|
|
|
|
@section('subcontent')
|
|
<div class="mypage-usage">
|
|
|
|
@if(session('success'))
|
|
<div class="notice-box notice-box--ok">{{ session('success') }}</div>
|
|
@endif
|
|
@if(session('error'))
|
|
<div class="notice-box notice-box--err">{{ session('error') }}</div>
|
|
@endif
|
|
|
|
<div class="usage-card">
|
|
|
|
{{-- 검색/필터 --}}
|
|
<form class="filters" method="get" action="{{ route('web.mypage.usage.index') }}">
|
|
<input class="inp inp--grow" name="q" value="{{ $filters['q'] ?? '' }}" placeholder="주문번호 검색">
|
|
|
|
<select class="sel" name="method">
|
|
<option value="">결제수단(전체)</option>
|
|
<option value="card" @selected(($filters['method'] ?? '')==='card')>카드</option>
|
|
<option value="phone" @selected(($filters['method'] ?? '')==='phone')>휴대폰</option>
|
|
<option value="wire" @selected(($filters['method'] ?? '')==='wire')>계좌이체</option>
|
|
<option value="vact" @selected(($filters['method'] ?? '')==='vact')>가상계좌</option>
|
|
</select>
|
|
|
|
<select class="sel" name="status">
|
|
<option value="">상태(전체)</option>
|
|
<option value="paid" @selected(($filters['status'] ?? '')==='paid')>결제완료</option>
|
|
<option value="cancelled" @selected(($filters['status'] ?? '')==='cancelled')>취소</option>
|
|
{{-- <option value="failed" @selected(($filters['status'] ?? '')==='failed')>실패</option>--}}
|
|
<option value="issued" @selected(($filters['status'] ?? '')==='issued')>입금대기</option>
|
|
{{-- <option value="ready" @selected(($filters['status'] ?? '')==='ready')>대기</option>--}}
|
|
{{-- <option value="redirected" @selected(($filters['status'] ?? '')==='redirected')>진행중</option>--}}
|
|
</select>
|
|
|
|
<div class="dates">
|
|
<input class="inp" type="date" name="from" value="{{ $filters['from'] ?? '' }}">
|
|
<span class="tilde">~</span>
|
|
<input class="inp" type="date" name="to" value="{{ $filters['to'] ?? '' }}">
|
|
</div>
|
|
|
|
<div class="btns">
|
|
<button class="btn btn--primary" type="submit">검색</button>
|
|
<a class="btn btn--primary" href="{{ route('web.mypage.usage.index') }}">초기화</a>
|
|
</div>
|
|
</form>
|
|
|
|
{{-- 모바일: 카드 리스트 (가로 스크롤 없음) --}}
|
|
<div class="list-mobile">
|
|
@forelse(($rows ?? []) as $idx => $r)
|
|
@php
|
|
$no = (method_exists($rows, 'firstItem') && $rows->firstItem())
|
|
? ($rows->firstItem() + $idx)
|
|
: ($idx + 1);
|
|
|
|
$name = (string)($r->product_name ?? '');
|
|
$item = (string)($r->item_name ?? '');
|
|
$qty = (int)($r->total_qty ?? 0);
|
|
$money = (int)($r->pay_money ?? 0);
|
|
$method = (string)($r->pay_method ?? '');
|
|
$paySt = $payStatusLabel($r);
|
|
$payStCls = $payStatusClass($paySt);
|
|
$issueSt = $issueStatusLabel($r);
|
|
$issueStCls = $issueStatusClass($issueSt);
|
|
$dt = $formatDate($r->created_at ?? '');
|
|
$href = route('web.mypage.usage.show', array_merge(['attemptId' => $r->attempt_id], $listQuery));
|
|
@endphp
|
|
|
|
<a class="mcard" href="{{ $href }}">
|
|
<div class="mcard__top">
|
|
<div class="mcard__no">No. {{ $no }}</div>
|
|
<div class="mcard__badges">
|
|
<span class="pill {{ $payStCls }}">{{ $paySt }}</span>
|
|
<span class="pill {{ $issueStCls }}">{{ $issueSt }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mcard__title">
|
|
{{ $name !== '' ? $name : '-' }}
|
|
</div>
|
|
|
|
<div class="mcard__meta">
|
|
<div class="mrow">
|
|
<span class="k">결제수단</span>
|
|
<span class="v">{{ $methodLabel($method) }}</span>
|
|
</div>
|
|
<div class="mrow">
|
|
<span class="k">수량</span>
|
|
<span class="v">{{ $qty }}</span>
|
|
</div>
|
|
<div class="mrow">
|
|
<span class="k">금액</span>
|
|
<span class="v">{{ number_format($money) }}원</span>
|
|
</div>
|
|
<div class="mrow">
|
|
<span class="k">일시</span>
|
|
<span class="v">{{ $dt }}</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
@empty
|
|
<div class="empty">구매내역이 없습니다.</div>
|
|
@endforelse
|
|
</div>
|
|
|
|
{{-- 데스크톱: 테이블 (행 클릭으로 상세 이동) --}}
|
|
<div class="list-desktop">
|
|
<table class="tbl">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:70px;">No.</th>
|
|
<th>상품명</th>
|
|
<th style="width:110px;">결제수단</th>
|
|
<th style="width:80px;">수량</th>
|
|
<th style="width:130px;">금액</th>
|
|
<th style="width:120px;">결제</th>
|
|
<th style="width:120px;">상태</th>
|
|
<th style="width:160px;">일시</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse(($rows ?? []) as $idx => $r)
|
|
@php
|
|
$no = (method_exists($rows, 'firstItem') && $rows->firstItem())
|
|
? ($rows->firstItem() + $idx)
|
|
: ($idx + 1);
|
|
|
|
$name = (string)($r->product_name ?? '');
|
|
$item = (string)($r->item_name ?? '');
|
|
$qty = (int)($r->total_qty ?? 0);
|
|
$money = (int)($r->pay_money ?? 0);
|
|
$method = (string)($r->pay_method ?? '');
|
|
$paySt = $payStatusLabel($r);
|
|
$payStCls = $payStatusClass($paySt);
|
|
$issueSt = $issueStatusLabel($r);
|
|
$issueStCls = $issueStatusClass($issueSt);
|
|
$dt = $formatDate($r->created_at ?? '');
|
|
$href = route('web.mypage.usage.show', array_merge(['attemptId' => $r->attempt_id], $listQuery));
|
|
@endphp
|
|
<tr class="row-link" data-href="{{ $href }}" tabindex="0" role="link" aria-label="상세 보기">
|
|
<td>{{ $no }}</td>
|
|
<td class="p_name">{{ $name !== '' ? $name : '-' }} - {{ $item !== '' ? $item : '-' }}</td>
|
|
<td>{{ $methodLabel($method) }}</td>
|
|
<td>{{ $qty }}</td>
|
|
<td class="money">{{ number_format($money) }} 원</td>
|
|
<td><span class="pill {{ $payStCls }}">{{ $paySt }}</span></td>
|
|
<td><span class="pill {{ $issueStCls }}">{{ $issueSt }}</span></td>
|
|
<td>{{ $dt }}</td>
|
|
</tr>
|
|
@empty
|
|
<tr><td colspan="8" class="empty">구매내역이 없습니다.</td></tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
@if($rows->hasPages())
|
|
<div class="mq-pager">
|
|
{{ $rows->links('web.partials.pagination') }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 데스크톱 테이블: 행 클릭 → 상세 이동
|
|
document.addEventListener('click', function (e) {
|
|
const tr = e.target.closest('.row-link');
|
|
if (!tr) return;
|
|
const href = tr.getAttribute('data-href');
|
|
if (href) window.location.href = href;
|
|
});
|
|
// 키보드 접근성(Enter) 지원
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key !== 'Enter') return;
|
|
const tr = e.target.closest('.row-link');
|
|
if (!tr) return;
|
|
const href = tr.getAttribute('data-href');
|
|
if (href) window.location.href = href;
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.mypage-usage { display:flex; flex-direction:column; gap:14px; }
|
|
.usage-card { border:1px solid rgba(0,0,0,.08); border-radius:14px; padding:16px; background:#fff; }
|
|
|
|
.head{display:flex; justify-content:space-between; align-items:flex-end; gap:10px; flex-wrap:wrap;}
|
|
.title-wrap{display:flex; flex-direction:column; gap:6px;}
|
|
.card-title { font-size:16px; margin:0; }
|
|
.muted{color:rgba(0,0,0,.55);}
|
|
|
|
/* Filters */
|
|
.filters{display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin:12px 0;}
|
|
.inp,.sel{padding:10px 10px;border-radius:12px;border:1px solid rgba(0,0,0,.14); background:#fff; font-size:13px;}
|
|
.inp--grow{flex:1; min-width:220px;}
|
|
.dates{display:flex;align-items:center;gap:6px;}
|
|
.tilde{opacity:.6;}
|
|
.btns{display:flex;gap:8px;}
|
|
.btn{display:inline-flex;align-items:center;justify-content:center;padding:10px 12px;border-radius:12px;border:1px solid rgba(0,0,0,.14);cursor:pointer;text-decoration:none;font-size:13px; white-space:nowrap;}
|
|
.btn--primary{font-weight:800;height:38px;margin-top:2px}
|
|
|
|
/* Pills */
|
|
.pill{display:inline-flex; padding:4px 10px; border-radius:999px; font-size:12px; border:1px solid rgba(0,0,0,.12); white-space:nowrap;}
|
|
.pill--ok{background:rgba(0,160,60,.08); border-color:rgba(0,160,60,.18);}
|
|
.pill--danger{background:rgba(220,0,0,.08); border-color:rgba(220,0,0,.18); color:rgb(180,0,0);}
|
|
.pill--wait{background:rgba(255,190,0,.12); border-color:rgba(255,190,0,.25);}
|
|
.pill--muted{opacity:.75;}
|
|
|
|
/* Desktop table */
|
|
.list-desktop{display:none;}
|
|
.tbl{width:100%; border-collapse:collapse;}
|
|
.tbl th,.tbl td{border-bottom:1px solid rgba(0,0,0,.08); padding:12px 10px; text-align:left; font-size:13px; vertical-align:middle;}
|
|
.tbl th{background:rgba(0,0,0,.02); font-weight:800;}
|
|
.row-link{cursor:pointer;}
|
|
.row-link:hover{background:rgba(0,0,0,.02);}
|
|
.row-link:focus{outline:2px solid rgba(0,0,0,.15); outline-offset:-2px;}
|
|
.mono{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;}
|
|
|
|
/* Mobile cards (no horizontal scroll) */
|
|
.list-mobile{display:flex; flex-direction:column; gap:10px;}
|
|
.mcard{
|
|
display:block;
|
|
text-decoration:none;
|
|
color:inherit;
|
|
border:1px solid rgba(0,0,0,.08);
|
|
border-radius:14px;
|
|
padding:12px;
|
|
background:#fff;
|
|
}
|
|
.mcard:active{transform:scale(0.99);}
|
|
.mcard__top{display:flex; justify-content:space-between; align-items:center; gap:10px;}
|
|
.mcard__no{font-size:12px; color:rgba(0,0,0,.6); font-weight:700;}
|
|
.mcard__title{margin-top:8px; font-size:14px; font-weight:800; line-height:1.35;}
|
|
.mcard__meta{margin-top:10px; display:flex; flex-direction:column; gap:6px;}
|
|
.mrow{display:flex; justify-content:space-between; gap:10px; font-size:13px;}
|
|
.mrow .k{color:rgba(0,0,0,.55);}
|
|
.mrow .v{font-weight:700;}
|
|
|
|
.empty{text-align:center;color:rgba(0,0,0,.55); padding:14px 0;}
|
|
.pager{margin-top:14px;}
|
|
|
|
.notice-box{padding:12px;border-radius:12px;background:rgba(0,0,0,.04);}
|
|
.notice-box--ok{background:rgba(0,160,60,.08);}
|
|
.notice-box--err{background:rgba(220,0,0,.08);}
|
|
|
|
.tbl th, .tbl td { text-align: center; }
|
|
.tbl td.p_name { text-align: left; }
|
|
.tbl td.money { text-align: right; }
|
|
|
|
/* Desktop breakpoint */
|
|
@media (min-width: 960px){
|
|
.list-mobile{display:none;}
|
|
.list-desktop{display:block;}
|
|
}
|
|
.mcard__badges{display:flex; gap:6px; flex-wrap:wrap; justify-content:flex-end;}
|
|
</style>
|
|
@endsection
|