341 lines
20 KiB
PHP
341 lines
20 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('title', '판매 코드 관리')
|
|
@section('page_title', 'API 연동 판매 코드 관리')
|
|
@section('page_desc', '외부 연동사(다날 등)와 해당 업체의 고유 상품 코드를 매핑합니다.')
|
|
|
|
@push('head')
|
|
<style>
|
|
.grid-layout { display: grid; grid-template-columns: 1fr 350px; gap: 20px; align-items: start; }
|
|
@media (max-width: 980px) { .grid-layout { grid-template-columns: 1fr; } }
|
|
|
|
.cat-list { background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.10); border-radius: 12px; }
|
|
.cat-item { padding: 10px 16px; border-bottom: 1px solid rgba(255,255,255,.05); display: flex; justify-content: space-between; align-items: center; transition: background 0.2s; }
|
|
.cat-group:last-child > .cat-item, .cat-children > .cat-item:last-child { border-bottom: none; }
|
|
.depth-2 { padding-left: 40px; background: rgba(0,0,0,.15); }
|
|
|
|
.cat-info { display: flex; align-items: center; gap: 10px; }
|
|
.cat-actions { display: flex; align-items: center; gap: 4px; }
|
|
.btn-sort { padding: 4px 8px; font-size: 11px; }
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="grid-layout">
|
|
<div class="a-card" style="padding:16px;">
|
|
<div style="font-weight:900; font-size:16px; margin-bottom:16px;">🏢 연동사 및 📦 상품코드 목록</div>
|
|
|
|
<div class="cat-list">
|
|
@forelse($tree as $pv)
|
|
<div class="cat-group">
|
|
<div class="cat-item" style="background: rgba(59,130,246,.1);">
|
|
<div class="cat-info">
|
|
<span class="pill {{ $pv['is_active'] ? 'pill--ok' : 'pill--muted' }}">
|
|
{{ $pv['is_active'] ? 'ON' : 'OFF' }}
|
|
</span>
|
|
<strong style="color:#60a5fa;">{{ $pv['name'] }}</strong>
|
|
<span class="mono">[{{ $pv['code'] }}]</span>
|
|
</div>
|
|
<div class="cat-actions">
|
|
<button type="button" class="lbtn lbtn--sm lbtn--ghost" onclick="editProvider({{ json_encode($pv) }})">연동사 수정</button>
|
|
<form action="{{ route('admin.sale-codes.provider.destroy', $pv['id']) }}" method="POST" onsubmit="return confirm('이 연동사를 삭제하시겠습니까?\n하위에 등록된 상품 코드가 없어야 삭제 가능합니다.');" style="margin:0;">
|
|
@csrf @method('DELETE')
|
|
<button type="submit" class="lbtn lbtn--sm lbtn--danger">삭제</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cat-children">
|
|
@foreach($pv['children'] as $cd)
|
|
<div class="cat-item depth-2" data-id="{{ $cd['id'] }}">
|
|
<div class="cat-info">
|
|
└
|
|
<span class="pill {{ $cd['is_active'] ? 'pill--ok' : 'pill--muted' }}">
|
|
{{ $cd['is_active'] ? 'ON' : 'OFF' }}
|
|
</span>
|
|
<span>{{ $cd['name'] }}</span>
|
|
<span class="a-muted" style="font-size:12px;">({{ $cd['api_code'] }})</span>
|
|
</div>
|
|
<div class="cat-actions">
|
|
<button type="button" class="lbtn lbtn--ghost btn-sort" onclick="moveRow(this, -1)">▲</button>
|
|
<button type="button" class="lbtn lbtn--ghost btn-sort" onclick="moveRow(this, 1)">▼</button>
|
|
<button type="button" class="lbtn lbtn--sm lbtn--ghost" style="margin-left:8px;" onclick="editCode({{ json_encode($cd) }})">수정</button>
|
|
<form action="{{ route('admin.sale-codes.code.destroy', $cd['id']) }}" method="POST" onsubmit="return confirm('이 상품 코드를 매핑에서 삭제하시겠습니까?');" style="margin:0;">
|
|
@csrf @method('DELETE')
|
|
<button type="submit" class="lbtn lbtn--sm lbtn--danger">삭제</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div style="padding: 20px; text-align: center;" class="a-muted">등록된 연동사 및 코드가 없습니다.</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="a-card" style="padding:16px; margin-bottom:20px; border-top: 3px solid rgba(52,211,153,.8);">
|
|
<div style="font-weight:900; font-size:16px; margin-bottom:16px;" id="codeTitle">📦 상품 코드 등록</div>
|
|
|
|
<form id="codeForm" method="POST" action="{{ route('admin.sale-codes.code.store') }}">
|
|
@csrf
|
|
<input type="hidden" name="_method" id="codeMethod" value="POST">
|
|
|
|
<div style="display:grid; gap:12px;">
|
|
<div class="a-field">
|
|
<label class="a-label">연동사 선택</label>
|
|
<select class="a-input" name="provider_id" id="codeProviderId" required>
|
|
<option value="">-- 연동사 선택 --</option>
|
|
@foreach($tree as $pv)
|
|
<option value="{{ $pv['id'] }}">{{ $pv['name'] }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div class="a-field">
|
|
<label class="a-label">실제 API 상품 코드 (예: CULTURE)</label>
|
|
<input class="a-input" name="api_code" id="codeApi" required>
|
|
</div>
|
|
<div class="a-field">
|
|
<label class="a-label">상품 매핑 명칭 (예: 문화상품권)</label>
|
|
<input class="a-input" name="name" id="codeName" required>
|
|
</div>
|
|
<div class="a-field">
|
|
<label class="a-label">사용 여부</label>
|
|
<select class="a-input" name="is_active" id="codeActive">
|
|
<option value="1">ON (사용)</option>
|
|
<option value="0">OFF (숨김)</option>
|
|
</select>
|
|
</div>
|
|
<div style="display:flex; gap:10px; margin-top:10px;">
|
|
<button type="button" class="lbtn lbtn--ghost" style="flex:1;" onclick="resetCodeForm()">신규등록 초기화</button>
|
|
<button type="submit" class="lbtn lbtn--primary" style="flex:1;">저장</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="a-card" style="padding:16px; border-top: 3px solid rgba(96,165,250,.8);">
|
|
<div style="font-weight:900; font-size:16px; margin-bottom:16px;" id="pvTitle">🏢 신규 연동사 (Provider) 등록</div>
|
|
|
|
<form id="pvForm" method="POST" action="{{ route('admin.sale-codes.provider.store') }}">
|
|
@csrf
|
|
<input type="hidden" name="_method" id="pvMethod" value="POST">
|
|
|
|
<div style="display:grid; gap:12px;">
|
|
<div class="a-field">
|
|
<label class="a-label">연동사 코드 (예: DANAL)</label>
|
|
<input class="a-input" name="code" id="pvCode" required>
|
|
</div>
|
|
|
|
<div class="a-field">
|
|
<label class="a-label">연동사 노출명</label>
|
|
<input class="a-input" name="name" id="pvName" required>
|
|
</div>
|
|
|
|
<div class="a-field">
|
|
<label class="a-label">전송 방식</label>
|
|
<select class="a-input" name="transport_type" id="pvTransportType" required>
|
|
<option value="HTTP_FORM">HTTP_FORM</option>
|
|
<option value="HTTP_ENCRYPTED">HTTP_ENCRYPTED</option>
|
|
<option value="TCP_SOCKET">TCP_SOCKET</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="a-field">
|
|
<label class="a-label">Base URL (HTTP 계열만)</label>
|
|
<input class="a-input" name="base_url" id="pvBaseUrl" placeholder="https://api.example.com">
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 120px; gap:12px;">
|
|
<div class="a-field">
|
|
<label class="a-label">Host (TCP 계열만)</label>
|
|
<input class="a-input" name="host" id="pvHost" placeholder="127.0.0.1">
|
|
</div>
|
|
<div class="a-field">
|
|
<label class="a-label">Port</label>
|
|
<input class="a-input" type="number" name="port" id="pvPort" min="1" max="65535">
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
|
<div class="a-field">
|
|
<label class="a-label">연결 타임아웃(초)</label>
|
|
<input class="a-input" type="number" name="timeout_connect_sec" id="pvTimeoutConnect" value="5" min="1" max="120" required>
|
|
</div>
|
|
<div class="a-field">
|
|
<label class="a-label">응답 타임아웃(초)</label>
|
|
<input class="a-input" type="number" name="timeout_read_sec" id="pvTimeoutRead" value="10" min="1" max="300" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
|
<div class="a-field">
|
|
<label class="a-label">문자셋</label>
|
|
<input class="a-input" name="charset" id="pvCharset" value="UTF-8" required>
|
|
</div>
|
|
<div class="a-field">
|
|
<label class="a-label">기본 응답 포맷</label>
|
|
<input class="a-input" name="response_format_default" id="pvResponseFormat" placeholder="JSON / XML / TEXT">
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
|
<div class="a-field">
|
|
<label class="a-label">테스트 모드</label>
|
|
<select class="a-input" name="is_test_mode" id="pvTestMode">
|
|
<option value="0">운영</option>
|
|
<option value="1">테스트</option>
|
|
</select>
|
|
</div>
|
|
<div class="a-field">
|
|
<label class="a-label">사용 여부</label>
|
|
<select class="a-input" name="is_active" id="pvActive">
|
|
<option value="1">ON (전체 사용)</option>
|
|
<option value="0">OFF (전체 장애/중단)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
|
<label class="a-check"><input type="hidden" name="supports_issue" value="0"><input type="checkbox" name="supports_issue" id="pvSupportsIssue" value="1" checked> 발행 지원</label>
|
|
<label class="a-check"><input type="hidden" name="supports_cancel" value="0"><input type="checkbox" name="supports_cancel" id="pvSupportsCancel" value="1"> 관리자 취소 지원</label>
|
|
<label class="a-check"><input type="hidden" name="supports_resend" value="0"><input type="checkbox" name="supports_resend" id="pvSupportsResend" value="1"> 재발송 지원</label>
|
|
<label class="a-check"><input type="hidden" name="supports_cancel_check" value="0"><input type="checkbox" name="supports_cancel_check" id="pvSupportsCancelCheck" value="1"> 취소가능조회 지원</label>
|
|
<label class="a-check"><input type="hidden" name="supports_network_cancel" value="0"><input type="checkbox" name="supports_network_cancel" id="pvSupportsNetworkCancel" value="1"> 망취소 지원</label>
|
|
</div>
|
|
|
|
<div class="a-field">
|
|
<label class="a-label">업체별 상세 설정 JSON</label>
|
|
<textarea class="a-input" name="config_json" id="pvConfigJson" rows="14" style="font-family:Consolas, monospace;"
|
|
placeholder='{
|
|
"cp_id": "",
|
|
"cp_pwd_enc": "",
|
|
"subcpid": "",
|
|
"crypto_key_enc": "",
|
|
"crypto_iv_enc": ""
|
|
}'></textarea>
|
|
<div class="a-help">업체별 인증값/경로/전문 기본값은 JSON으로 저장합니다.</div>
|
|
</div>
|
|
|
|
<div style="display:flex; gap:10px; margin-top:10px;">
|
|
<button type="button" class="lbtn lbtn--ghost" style="flex:1;" onclick="resetPvForm()">신규등록 초기화</button>
|
|
<button type="submit" class="lbtn lbtn--primary" style="flex:1;">저장</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
const baseUrl = '{{ route('admin.sale-codes.index') }}';
|
|
|
|
function editCode(cd) {
|
|
document.getElementById('codeTitle').innerText = '📦 상품 코드 수정 (#'+cd.id+')';
|
|
document.getElementById('codeForm').action = baseUrl + '/code/' + cd.id;
|
|
document.getElementById('codeMethod').value = 'PUT';
|
|
|
|
document.getElementById('codeProviderId').value = cd.provider_id;
|
|
document.getElementById('codeApi').value = cd.api_code;
|
|
document.getElementById('codeName').value = cd.name;
|
|
document.getElementById('codeActive').value = cd.is_active;
|
|
}
|
|
|
|
function resetCodeForm() {
|
|
document.getElementById('codeTitle').innerText = '📦 상품 코드 등록';
|
|
document.getElementById('codeForm').action = '{{ route('admin.sale-codes.code.store') }}';
|
|
document.getElementById('codeMethod').value = 'POST';
|
|
|
|
document.getElementById('codeProviderId').value = '';
|
|
document.getElementById('codeApi').value = '';
|
|
document.getElementById('codeName').value = '';
|
|
document.getElementById('codeActive').value = '1';
|
|
}
|
|
|
|
function editProvider(pv) {
|
|
document.getElementById('pvTitle').innerText = '🏢 연동사 수정 (#' + pv.id + ')';
|
|
document.getElementById('pvForm').action = baseUrl + '/provider/' + pv.id;
|
|
document.getElementById('pvMethod').value = 'PUT';
|
|
|
|
document.getElementById('pvCode').value = pv.code || '';
|
|
document.getElementById('pvCode').readOnly = true;
|
|
document.getElementById('pvName').value = pv.name || '';
|
|
document.getElementById('pvTransportType').value = pv.transport_type || 'HTTP_FORM';
|
|
document.getElementById('pvBaseUrl').value = pv.base_url || '';
|
|
document.getElementById('pvHost').value = pv.host || '';
|
|
document.getElementById('pvPort').value = pv.port || '';
|
|
document.getElementById('pvTimeoutConnect').value = pv.timeout_connect_sec || 5;
|
|
document.getElementById('pvTimeoutRead').value = pv.timeout_read_sec || 10;
|
|
document.getElementById('pvCharset').value = pv.charset || 'UTF-8';
|
|
document.getElementById('pvResponseFormat').value = pv.response_format_default || '';
|
|
document.getElementById('pvTestMode').value = String(pv.is_test_mode ?? 0);
|
|
document.getElementById('pvActive').value = String(pv.is_active ?? 1);
|
|
|
|
document.getElementById('pvSupportsIssue').checked = Number(pv.supports_issue ?? 1) === 1;
|
|
document.getElementById('pvSupportsCancel').checked = Number(pv.supports_cancel ?? 0) === 1;
|
|
document.getElementById('pvSupportsResend').checked = Number(pv.supports_resend ?? 0) === 1;
|
|
document.getElementById('pvSupportsCancelCheck').checked = Number(pv.supports_cancel_check ?? 0) === 1;
|
|
document.getElementById('pvSupportsNetworkCancel').checked = Number(pv.supports_network_cancel ?? 0) === 1;
|
|
|
|
document.getElementById('pvConfigJson').value = pv.config_json || '';
|
|
}
|
|
|
|
function resetPvForm() {
|
|
document.getElementById('pvTitle').innerText = '🏢 신규 연동사 등록';
|
|
document.getElementById('pvForm').action = '{{ route('admin.sale-codes.provider.store') }}';
|
|
document.getElementById('pvMethod').value = 'POST';
|
|
|
|
document.getElementById('pvCode').value = '';
|
|
document.getElementById('pvCode').readOnly = false;
|
|
document.getElementById('pvName').value = '';
|
|
document.getElementById('pvTransportType').value = 'HTTP_FORM';
|
|
document.getElementById('pvBaseUrl').value = '';
|
|
document.getElementById('pvHost').value = '';
|
|
document.getElementById('pvPort').value = '';
|
|
document.getElementById('pvTimeoutConnect').value = '5';
|
|
document.getElementById('pvTimeoutRead').value = '10';
|
|
document.getElementById('pvCharset').value = 'UTF-8';
|
|
document.getElementById('pvResponseFormat').value = '';
|
|
document.getElementById('pvTestMode').value = '0';
|
|
document.getElementById('pvActive').value = '1';
|
|
|
|
document.getElementById('pvSupportsIssue').checked = true;
|
|
document.getElementById('pvSupportsCancel').checked = false;
|
|
document.getElementById('pvSupportsResend').checked = false;
|
|
document.getElementById('pvSupportsCancelCheck').checked = false;
|
|
document.getElementById('pvSupportsNetworkCancel').checked = false;
|
|
document.getElementById('pvConfigJson').value = '';
|
|
}
|
|
|
|
function moveRow(btn, direction) {
|
|
const item = btn.closest('.cat-item');
|
|
const container = item.parentNode;
|
|
|
|
if (direction === -1 && item.previousElementSibling) {
|
|
container.insertBefore(item, item.previousElementSibling);
|
|
saveSort(container);
|
|
} else if (direction === 1 && item.nextElementSibling) {
|
|
container.insertBefore(item.nextElementSibling, item);
|
|
saveSort(container);
|
|
}
|
|
}
|
|
|
|
function saveSort(container) {
|
|
const ids = Array.from(container.children).map(el => el.getAttribute('data-id'));
|
|
fetch('{{ route('admin.sale-codes.code.sort') }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({ ids: ids })
|
|
});
|
|
}
|
|
</script>
|
|
@endpush
|