giftcon_dev/resources/views/admin/log/MemberAccountLogController.blade.php
sungro815 b0545ab5b9 관리자 상품관리 완료
웹사이트 상품리스트 상세보기 작업중
2026-02-20 18:11:03 +09:00

383 lines
18 KiB
PHP

@extends('admin.layouts.app')
@section('title', '계좌 성명인증 로그')
@section('page_title', '계좌 성명인증 로그')
@section('page_desc', '더즌 성명인증 요청/결과 로그를 조회합니다.')
@section('content_class', 'a-content--full')
@push('head')
<style>
.bar{display:flex;justify-content:space-between;align-items:flex-end;gap:12px;flex-wrap:wrap;}
.filters{display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end;}
.filters .inp{width:160px;}
.filters .inpWide{width:220px;}
.filters .sel{width:140px;}
.lbtn{padding:8px 12px;font-size:13px;border-radius:12px;line-height:1.1;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);color:inherit;cursor:pointer;}
.lbtn:hover{background:rgba(255,255,255,.10);text-decoration:none;}
.lbtn--ghost{background:transparent;}
.lbtn--sm{padding:7px 10px;font-size:12px;border-radius:11px;}
.lbtn--primary{background:rgba(59,130,246,.88);border-color:rgba(59,130,246,.95);color:#fff;}
.lbtn--primary:hover{background:rgba(59,130,246,.98);}
.mono{padding:4px 8px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px;display:inline-block;}
.muted{opacity:.8;font-size:12px;}
.nowrap{white-space:nowrap;}
.ellipsis{max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;vertical-align:bottom;}
.badge{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);}
.badge--ok{border-color:rgba(34,197,94,.35);background:rgba(34,197,94,.10);}
.badge--bad{border-color:rgba(244,63,94,.35);background:rgba(244,63,94,.10);}
a.memlink{color:#fff;text-decoration:none;}
a.memlink:hover{color:#fff;text-decoration:underline;}
/* modal */
.mback{position:fixed;inset:0;display:none;align-items:center;justify-content:center;z-index:9999;
background:rgba(0,0,0,.55);backdrop-filter:blur(2px);}
.mback.is-open{display:flex;}
.modalx{width:min(980px, 92vw);max-height:88vh;overflow:hidden;border-radius:18px;
border:1px solid rgba(255,255,255,.12);background:rgba(18,18,18,.96);box-shadow:0 20px 60px rgba(0,0,0,.45);}
.modalx__head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;padding:16px 16px 10px;border-bottom:1px solid rgba(255,255,255,.10);}
.modalx__title{font-weight:900;font-size:16px;}
.modalx__desc{font-size:12px;opacity:.8;margin-top:4px;}
.modalx__body{padding:16px;overflow:auto;max-height:70vh;}
.modalx__foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid rgba(255,255,255,.10);}
.kv{display:grid;grid-template-columns:170px 1fr;gap:8px;margin-bottom:12px;}
.kv .k{opacity:.75;font-size:12px;}
.kv .v{font-size:12px;word-break:break-all;}
.prebox{border:1px solid rgba(255,255,255,.10);border-radius:14px;background:rgba(255,255,255,.03);padding:12px;margin-top:12px;}
.prebox .t{font-weight:900;font-size:12px;opacity:.85;margin-bottom:8px;}
.prebox pre{margin:0;white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;}
</style>
@endpush
@section('content')
@php
$indexUrl = route('admin.systemlog.member-account-logs', [], false);
$f = $filters ?? [];
$dateFrom = (string)($f['date_from'] ?? '');
$dateTo = (string)($f['date_to'] ?? '');
$memNo = (string)($f['mem_no'] ?? '');
$bank = (string)($f['bank_code'] ?? '');
$status = (string)($f['status'] ?? '');
$account = (string)($f['account'] ?? '');
$name = (string)($f['name'] ?? '');
$q = (string)($f['q'] ?? '');
@endphp
<div class="a-card" style="padding:16px; margin-bottom:16px;">
<div class="bar">
<div>
<div style="font-weight:900;font-size:16px;">계좌 성명인증 로그</div>
<div class="muted" style="margin-top:4px;">요청/결과 JSON은 모달로 표시 (row 높이 고정)</div>
</div>
<form method="GET" action="{{ $indexUrl }}" class="filters">
<div>
<label class="muted">From</label>
<input class="a-input inp" type="date" name="date_from" value="{{ $dateFrom }}">
</div>
<div>
<label class="muted">To</label>
<input class="a-input inp" type="date" name="date_to" value="{{ $dateTo }}">
</div>
<div>
<label class="muted">mem_no</label>
<input class="a-input inp" name="mem_no" value="{{ $memNo }}" inputmode="numeric" placeholder="41970">
</div>
<div>
<label class="muted">bank_code</label>
<input class="a-input inp" name="bank_code" value="{{ $bank }}" placeholder="003">
</div>
<div>
<label class="muted">status</label>
<select class="a-input sel" name="status">
<option value="">전체</option>
<option value="ok" {{ $status==='ok'?'selected':'' }}>성공(200)</option>
<option value="fail" {{ $status==='fail'?'selected':'' }}>실패(!=200)</option>
<option value="200" {{ $status==='200'?'selected':'' }}>200</option>
<option value="520" {{ $status==='520'?'selected':'' }}>520</option>
</select>
</div>
<div>
<label class="muted">account</label>
<input class="a-input inpWide" name="account" value="{{ $account }}" placeholder="계좌 일부">
</div>
<div>
<label class="muted">name</label>
<input class="a-input inp" name="name" value="{{ $name }}" placeholder="예금주">
</div>
<div>
<label class="muted">q(JSON)</label>
<input class="a-input inpWide" name="q" value="{{ $q }}" placeholder="error_code / natv_tr_no ...">
</div>
<div style="display:flex;gap:8px;align-items:flex-end;">
<button class="lbtn lbtn--ghost" type="submit">검색</button>
<a class="lbtn lbtn--ghost" href="{{ $indexUrl }}">초기화</a>
</div>
</form>
</div>
</div>
<div class="a-card" style="padding:16px;">
<div class="a-muted" style="margin-bottom:10px;"> <b>{{ $page->total() }}</b></div>
<div style="overflow:auto;">
<table class="a-table table" style="width:100%; min-width:1400px;">
<thead>
<tr>
<th style="width:90px;">SEQ</th>
<th style="width:190px;">request_time</th>
<th style="width:190px;">result_time</th>
<th style="width:140px;">mem_no</th>
<th style="width:120px;">bank</th>
<th style="width:260px;">account</th>
<th style="width:180px;">name</th>
<th style="width:120px;">protype</th>
<th style="width:120px;">status</th>
<th style="width:220px;">depositor/error</th>
<th style="width:140px;">JSON</th>
</tr>
</thead>
<tbody>
@forelse(($items ?? []) as $r0)
@php
$r = is_array($r0) ? $r0 : (array)$r0;
$seq = (int)($r['seq'] ?? 0);
$memNoInt = (int)($r['mem_no'] ?? 0);
$memLink = $r['mem_link'] ?? null;
$bankCode = (string)($r['bank_code'] ?? '');
$acctMask = (string)($r['account_masked'] ?? '');
$nameV = (string)($r['mam_accountname'] ?? '');
$proType = (string)($r['account_protype'] ?? '');
$statusInt = (int)($r['status_int'] ?? 0);
$badge = (string)($r['status_badge'] ?? 'badge--bad');
$label = (string)($r['status_label'] ?? '');
$depositor = (string)($r['depositor'] ?? '');
$errCode = (string)($r['error_code'] ?? '');
$errMsg = (string)($r['error_message'] ?? '');
@endphp
<tr>
<td class="a-muted">{{ $seq }}</td>
<td class="a-muted nowrap">{{ $r['request_time'] ?? '-' }}</td>
<td class="a-muted nowrap">{{ $r['result_time'] ?? '-' }}</td>
<td>
@if($memNoInt > 0 && $memLink)
<a href="{{ $memLink }}" class="mono memlink" target="_blank" rel="noopener">{{ $memNoInt }}</a>
@else
<span class="mono">-</span>
@endif
</td>
<td>
@if($bankCode !== '')
<span class="mono">{{ $bankCode }}</span>
@else
<span class="a-muted">-</span>
@endif
</td>
<td>
@if($acctMask !== '')
<span class="mono">{{ $acctMask }}</span>
@else
<span class="a-muted">-</span>
@endif
</td>
<td>
@if($nameV !== '')
<span class="mono">{{ $nameV }}</span>
@else
<span class="a-muted">-</span>
@endif
</td>
<td>
@if($proType !== '')
<span class="mono">{{ $proType }}</span>
@else
<span class="a-muted">-</span>
@endif
</td>
<td>
<span class="badge {{ $badge }}">
{{ $label }} ({{ $statusInt }})
</span>
</td>
<td>
@if($depositor !== '')
<span class="mono ellipsis" title="{{ $depositor }}">{{ $depositor }}</span>
@elseif($errCode !== '' || $errMsg !== '')
<span class="mono ellipsis" title="{{ trim($errCode.' '.$errMsg) }}">{{ trim($errCode.' '.$errMsg) }}</span>
@else
<span class="a-muted">-</span>
@endif
</td>
<td class="nowrap" style="text-align:right;">
<button type="button"
class="lbtn lbtn--ghost lbtn--sm btnJson"
data-seq="{{ $seq }}"
data-mem="{{ $memNoInt }}"
data-bank="{{ e($bankCode) }}"
data-account="{{ e((string)($r['account'] ?? '')) }}"
data-name="{{ e($nameV) }}"
data-protype="{{ e($proType) }}"
data-status="{{ $statusInt }}"
data-err="{{ e(trim($errCode.' '.$errMsg)) }}"
data-depositor="{{ e($depositor) }}">
JSON 보기
</button>
<textarea id="reqStore-{{ $seq }}" style="display:none;">{{ $r['request_pretty'] ?? ($r['request_data'] ?? '') }}</textarea>
<textarea id="resStore-{{ $seq }}" style="display:none;">{{ $r['result_pretty'] ?? ($r['result_data'] ?? '') }}</textarea>
</td>
</tr>
@empty
<tr><td colspan="11" class="a-muted" style="padding:16px;">데이터가 없습니다.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div style="margin-top:12px;">
{{ $page->onEachSide(1)->links('vendor.pagination.admin') }}
</div>
</div>
{{-- JSON Modal --}}
<div class="mback" id="jsonModalBack" aria-hidden="true">
<div class="modalx" role="dialog" aria-modal="true" aria-labelledby="jsonModalTitle">
<div class="modalx__head">
<div>
<div class="modalx__title" id="jsonModalTitle">계좌 성명인증 JSON</div>
<div class="modalx__desc" id="jsonModalDesc">요청/결과 JSON</div>
</div>
<button class="lbtn lbtn--ghost lbtn--sm" type="button" id="btnJsonClose">닫기 </button>
</div>
<div class="modalx__body">
<div class="kv" id="jsonModalKv"></div>
<div class="prebox">
<div class="t">Request JSON</div>
<pre id="jsonReqPre"></pre>
</div>
<div class="prebox">
<div class="t">Result JSON</div>
<pre id="jsonResPre"></pre>
</div>
</div>
<div class="modalx__foot">
<button class="lbtn lbtn--ghost" type="button" id="btnJsonCopy">복사</button>
<button class="lbtn lbtn--primary" type="button" id="btnJsonOk">확인</button>
</div>
</div>
</div>
<script>
(function(){
const back = document.getElementById('jsonModalBack');
const kv = document.getElementById('jsonModalKv');
const preReq = document.getElementById('jsonReqPre');
const preRes = document.getElementById('jsonResPre');
const btnClose = document.getElementById('btnJsonClose');
const btnOk = document.getElementById('btnJsonOk');
const btnCopy = document.getElementById('btnJsonCopy');
const open = () => {
back.classList.add('is-open');
back.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
};
const close = () => {
back.classList.remove('is-open');
back.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
};
btnClose?.addEventListener('click', close);
btnOk?.addEventListener('click', close);
back?.addEventListener('click', (e)=>{ if(e.target === back) close(); });
document.addEventListener('keydown', (e)=>{ if(e.key === 'Escape' && back.classList.contains('is-open')) close(); });
btnCopy?.addEventListener('click', async () => {
const text = (preReq.textContent || '') + "\n\n" + (preRes.textContent || '');
try {
await navigator.clipboard.writeText(text);
btnCopy.textContent = '복사됨';
setTimeout(()=>btnCopy.textContent='복사', 900);
} catch (e) {
alert('복사를 지원하지 않는 환경입니다.');
}
});
const renderKv = (pairs) => {
kv.innerHTML = '';
pairs.filter(p => p.v && String(p.v).trim() !== '').forEach(p => {
const k = document.createElement('div'); k.className='k'; k.textContent=p.k;
const v = document.createElement('div'); v.className='v';
const span = document.createElement('span'); span.className='mono'; span.textContent=p.v;
v.appendChild(span);
kv.appendChild(k); kv.appendChild(v);
});
};
document.querySelectorAll('.btnJson').forEach(btn => {
btn.addEventListener('click', () => {
const seq = btn.getAttribute('data-seq');
if (!seq) return;
const reqTa = document.getElementById('reqStore-' + seq);
const resTa = document.getElementById('resStore-' + seq);
const req = reqTa ? reqTa.value : '';
const res = resTa ? resTa.value : '';
renderKv([
{k:'mem_no', v: btn.getAttribute('data-mem') || ''},
{k:'bank_code', v: btn.getAttribute('data-bank') || ''},
{k:'account', v: btn.getAttribute('data-account') || ''},
{k:'name', v: btn.getAttribute('data-name') || ''},
{k:'protype', v: btn.getAttribute('data-protype') || ''},
{k:'status', v: btn.getAttribute('data-status') || ''},
{k:'depositor', v: btn.getAttribute('data-depositor') || ''},
{k:'error', v: btn.getAttribute('data-err') || ''},
]);
preReq.textContent = req || '';
preRes.textContent = res || '';
open();
});
});
})();
</script>
@endsection