feat:계좌 입출금내역 조회 페이지 추가
- EaccountController.php: 바로빌 BANKACCOUNT.asmx SOAP API 연동 - GetBankAccountEx: 등록된 계좌 목록 조회 - GetPeriodBankAccountTransLog: 계좌 입출금내역 조회 - index.blade.php: React 기반 UI (전자세금계산서와 동일 구조) - 테넌트 정보 카드 - 통계 카드 (입금/출금/계좌수/거래건수) - 계좌 선택 버튼 - 기간 조회 필터 (이번달/지난달 버튼) - 입출금 내역 테이블 (스크롤) - 라우트 추가: /barobill/eaccount - 메뉴 시더 업데이트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
587
app/Http/Controllers/Barobill/EaccountController.php
Normal file
587
app/Http/Controllers/Barobill/EaccountController.php
Normal file
@@ -0,0 +1,587 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Barobill;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Barobill\BarobillConfig;
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* 바로빌 계좌 입출금내역 조회 컨트롤러
|
||||
*/
|
||||
class EaccountController extends Controller
|
||||
{
|
||||
/**
|
||||
* 바로빌 SOAP 설정
|
||||
*/
|
||||
private ?string $certKey = null;
|
||||
private ?string $corpNum = null;
|
||||
private bool $isTestMode = false;
|
||||
private ?string $soapUrl = null;
|
||||
private ?\SoapClient $soapClient = null;
|
||||
|
||||
// 바로빌 파트너사 (본사) 테넌트 ID
|
||||
private const HEADQUARTERS_TENANT_ID = 1;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// DB에서 활성화된 바로빌 설정 조회
|
||||
$activeConfig = BarobillConfig::where('is_active', true)->first();
|
||||
|
||||
if ($activeConfig) {
|
||||
$this->certKey = $activeConfig->cert_key;
|
||||
$this->corpNum = $activeConfig->corp_num;
|
||||
$this->isTestMode = $activeConfig->environment === 'test';
|
||||
// 계좌 조회는 BANKACCOUNT.asmx 사용
|
||||
$baseUrl = $this->isTestMode
|
||||
? 'https://testws.baroservice.com'
|
||||
: 'https://ws.baroservice.com';
|
||||
$this->soapUrl = $baseUrl . '/BANKACCOUNT.asmx?WSDL';
|
||||
} else {
|
||||
$this->certKey = config('services.barobill.cert_key', '');
|
||||
$this->corpNum = config('services.barobill.corp_num', '');
|
||||
$this->isTestMode = config('services.barobill.test_mode', true);
|
||||
$this->soapUrl = $this->isTestMode
|
||||
? 'https://testws.baroservice.com/BANKACCOUNT.asmx?WSDL'
|
||||
: 'https://ws.baroservice.com/BANKACCOUNT.asmx?WSDL';
|
||||
}
|
||||
|
||||
$this->initSoapClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* SOAP 클라이언트 초기화
|
||||
*/
|
||||
private function initSoapClient(): void
|
||||
{
|
||||
if (!empty($this->certKey) || $this->isTestMode) {
|
||||
try {
|
||||
$context = stream_context_create([
|
||||
'ssl' => [
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true
|
||||
]
|
||||
]);
|
||||
|
||||
$this->soapClient = new \SoapClient($this->soapUrl, [
|
||||
'trace' => true,
|
||||
'encoding' => 'UTF-8',
|
||||
'exceptions' => true,
|
||||
'connection_timeout' => 30,
|
||||
'stream_context' => $context,
|
||||
'cache_wsdl' => WSDL_CACHE_NONE
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('바로빌 계좌 SOAP 클라이언트 생성 실패: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 계좌 입출금내역 메인 페이지
|
||||
*/
|
||||
public function index(Request $request): View|Response
|
||||
{
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('barobill.eaccount.index'));
|
||||
}
|
||||
|
||||
// 현재 선택된 테넌트 정보
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$currentTenant = Tenant::find($tenantId);
|
||||
|
||||
// 해당 테넌트의 바로빌 회원사 정보
|
||||
$barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();
|
||||
|
||||
return view('barobill.eaccount.index', [
|
||||
'certKey' => $this->certKey,
|
||||
'corpNum' => $this->corpNum,
|
||||
'isTestMode' => $this->isTestMode,
|
||||
'hasSoapClient' => $this->soapClient !== null,
|
||||
'currentTenant' => $currentTenant,
|
||||
'barobillMember' => $barobillMember,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록된 계좌 목록 조회 (GetBankAccountEx)
|
||||
*/
|
||||
public function accounts(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$availOnly = $request->input('availOnly', 0);
|
||||
|
||||
// 현재 테넌트의 바로빌 회원 정보 조회
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();
|
||||
|
||||
// 바로빌 사용자 ID 결정
|
||||
$userId = $barobillMember?->barobill_id ?? '';
|
||||
|
||||
$result = $this->callSoap('GetBankAccountEx', [
|
||||
'AvailOnly' => (int)$availOnly
|
||||
]);
|
||||
|
||||
if (!$result['success']) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => $result['error'],
|
||||
'error_code' => $result['error_code'] ?? null
|
||||
]);
|
||||
}
|
||||
|
||||
$accounts = [];
|
||||
$data = $result['data'];
|
||||
|
||||
// BankAccount 또는 BankAccountEx에서 계좌 목록 추출
|
||||
$accountList = [];
|
||||
if (isset($data->BankAccount)) {
|
||||
$accountList = is_array($data->BankAccount) ? $data->BankAccount : [$data->BankAccount];
|
||||
} elseif (isset($data->BankAccountEx)) {
|
||||
$accountList = is_array($data->BankAccountEx) ? $data->BankAccountEx : [$data->BankAccountEx];
|
||||
}
|
||||
|
||||
foreach ($accountList as $acc) {
|
||||
if (!is_object($acc)) continue;
|
||||
|
||||
$bankAccountNum = $acc->BankAccountNum ?? '';
|
||||
if (empty($bankAccountNum) || (is_numeric($bankAccountNum) && $bankAccountNum < 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bankCode = $acc->BankCode ?? '';
|
||||
$bankName = $acc->BankName ?? $this->getBankName($bankCode);
|
||||
|
||||
$accounts[] = [
|
||||
'bankAccountNum' => $bankAccountNum,
|
||||
'bankCode' => $bankCode,
|
||||
'bankName' => $bankName,
|
||||
'accountName' => $acc->AccountName ?? '',
|
||||
'accountType' => $acc->AccountType ?? '',
|
||||
'currency' => $acc->Currency ?? 'KRW',
|
||||
'issueDate' => $acc->IssueDate ?? '',
|
||||
'balance' => $acc->Balance ?? 0,
|
||||
'status' => isset($acc->UseState) ? (int)$acc->UseState : 1
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'accounts' => $accounts,
|
||||
'count' => count($accounts)
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('계좌 목록 조회 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '서버 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 계좌 입출금내역 조회 (GetPeriodBankAccountTransLog)
|
||||
*/
|
||||
public function transactions(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$startDate = $request->input('startDate', date('Ymd'));
|
||||
$endDate = $request->input('endDate', date('Ymd'));
|
||||
$bankAccountNum = str_replace('-', '', $request->input('accountNum', ''));
|
||||
$page = (int)$request->input('page', 1);
|
||||
$limit = (int)$request->input('limit', 50);
|
||||
|
||||
// 현재 테넌트의 바로빌 회원 정보 조회
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$barobillMember = BarobillMember::where('tenant_id', $tenantId)->first();
|
||||
$userId = $barobillMember?->barobill_id ?? '';
|
||||
|
||||
// 전체 계좌 조회: 빈 값이면 모든 계좌의 거래 내역 조회
|
||||
if (empty($bankAccountNum)) {
|
||||
return $this->getAllAccountsTransactions($userId, $startDate, $endDate, $page, $limit);
|
||||
}
|
||||
|
||||
// 단일 계좌 조회
|
||||
$result = $this->callSoap('GetPeriodBankAccountTransLog', [
|
||||
'ID' => $userId,
|
||||
'BankAccountNum' => $bankAccountNum,
|
||||
'StartDate' => $startDate,
|
||||
'EndDate' => $endDate,
|
||||
'TransDirection' => 1, // 1:전체
|
||||
'CountPerPage' => $limit,
|
||||
'CurrentPage' => $page,
|
||||
'OrderDirection' => 2 // 2:내림차순
|
||||
]);
|
||||
|
||||
if (!$result['success']) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => $result['error'],
|
||||
'error_code' => $result['error_code'] ?? null
|
||||
]);
|
||||
}
|
||||
|
||||
$resultData = $result['data'];
|
||||
|
||||
// 에러 코드 체크
|
||||
$errorCode = $this->checkErrorCode($resultData);
|
||||
if ($errorCode && !in_array($errorCode, [-25005, -25001])) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => $this->getErrorMessage($errorCode),
|
||||
'error_code' => $errorCode
|
||||
]);
|
||||
}
|
||||
|
||||
// 데이터가 없는 경우
|
||||
if ($errorCode && in_array($errorCode, [-25005, -25001])) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'logs' => [],
|
||||
'summary' => ['totalDeposit' => 0, 'totalWithdraw' => 0, 'count' => 0],
|
||||
'pagination' => ['currentPage' => 1, 'maxPageNum' => 1]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// 데이터 파싱
|
||||
$logs = $this->parseTransactionLogs($resultData);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'logs' => $logs['logs'],
|
||||
'pagination' => [
|
||||
'currentPage' => $resultData->CurrentPage ?? 1,
|
||||
'countPerPage' => $resultData->CountPerPage ?? 50,
|
||||
'maxPageNum' => $resultData->MaxPageNum ?? 1,
|
||||
'maxIndex' => $resultData->MaxIndex ?? 0
|
||||
],
|
||||
'summary' => $logs['summary']
|
||||
]
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('입출금내역 조회 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '서버 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 계좌의 거래 내역 조회
|
||||
*/
|
||||
private function getAllAccountsTransactions(string $userId, string $startDate, string $endDate, int $page, int $limit): JsonResponse
|
||||
{
|
||||
// 먼저 계좌 목록 조회
|
||||
$accountResult = $this->callSoap('GetBankAccountEx', ['AvailOnly' => 0]);
|
||||
|
||||
if (!$accountResult['success']) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => $accountResult['error']
|
||||
]);
|
||||
}
|
||||
|
||||
$accountList = [];
|
||||
$data = $accountResult['data'];
|
||||
if (isset($data->BankAccount)) {
|
||||
$accountList = is_array($data->BankAccount) ? $data->BankAccount : [$data->BankAccount];
|
||||
} elseif (isset($data->BankAccountEx)) {
|
||||
$accountList = is_array($data->BankAccountEx) ? $data->BankAccountEx : [$data->BankAccountEx];
|
||||
}
|
||||
|
||||
$allLogs = [];
|
||||
$totalDeposit = 0;
|
||||
$totalWithdraw = 0;
|
||||
|
||||
foreach ($accountList as $acc) {
|
||||
if (!is_object($acc)) continue;
|
||||
|
||||
$accNum = $acc->BankAccountNum ?? '';
|
||||
if (empty($accNum) || (is_numeric($accNum) && $accNum < 0)) continue;
|
||||
|
||||
$accResult = $this->callSoap('GetPeriodBankAccountTransLog', [
|
||||
'ID' => $userId,
|
||||
'BankAccountNum' => $accNum,
|
||||
'StartDate' => $startDate,
|
||||
'EndDate' => $endDate,
|
||||
'TransDirection' => 1,
|
||||
'CountPerPage' => 1000,
|
||||
'CurrentPage' => 1,
|
||||
'OrderDirection' => 2
|
||||
]);
|
||||
|
||||
if ($accResult['success']) {
|
||||
$accData = $accResult['data'];
|
||||
$errorCode = $this->checkErrorCode($accData);
|
||||
|
||||
if (!$errorCode || in_array($errorCode, [-25005, -25001])) {
|
||||
$parsed = $this->parseTransactionLogs($accData, $acc->BankName ?? '');
|
||||
foreach ($parsed['logs'] as $log) {
|
||||
$log['bankName'] = $acc->BankName ?? $this->getBankName($acc->BankCode ?? '');
|
||||
$allLogs[] = $log;
|
||||
}
|
||||
$totalDeposit += $parsed['summary']['totalDeposit'];
|
||||
$totalWithdraw += $parsed['summary']['totalWithdraw'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 날짜/시간 기준 정렬 (최신순)
|
||||
usort($allLogs, function ($a, $b) {
|
||||
$dateA = ($a['transDate'] ?? '') . ($a['transTime'] ?? '');
|
||||
$dateB = ($b['transDate'] ?? '') . ($b['transTime'] ?? '');
|
||||
return strcmp($dateB, $dateA);
|
||||
});
|
||||
|
||||
// 페이지네이션
|
||||
$totalCount = count($allLogs);
|
||||
$maxPageNum = (int)ceil($totalCount / $limit);
|
||||
$startIndex = ($page - 1) * $limit;
|
||||
$paginatedLogs = array_slice($allLogs, $startIndex, $limit);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'logs' => $paginatedLogs,
|
||||
'pagination' => [
|
||||
'currentPage' => $page,
|
||||
'countPerPage' => $limit,
|
||||
'maxPageNum' => $maxPageNum,
|
||||
'maxIndex' => $totalCount
|
||||
],
|
||||
'summary' => [
|
||||
'totalDeposit' => $totalDeposit,
|
||||
'totalWithdraw' => $totalWithdraw,
|
||||
'count' => $totalCount
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래 내역 파싱
|
||||
*/
|
||||
private function parseTransactionLogs($resultData, string $defaultBankName = ''): array
|
||||
{
|
||||
$logs = [];
|
||||
$totalDeposit = 0;
|
||||
$totalWithdraw = 0;
|
||||
|
||||
$rawLogs = [];
|
||||
if (isset($resultData->BankAccountLogList) && isset($resultData->BankAccountLogList->BankAccountTransLog)) {
|
||||
$rawLogs = is_array($resultData->BankAccountLogList->BankAccountTransLog)
|
||||
? $resultData->BankAccountLogList->BankAccountTransLog
|
||||
: [$resultData->BankAccountLogList->BankAccountTransLog];
|
||||
}
|
||||
|
||||
foreach ($rawLogs as $log) {
|
||||
$deposit = floatval($log->Deposit ?? 0);
|
||||
$withdraw = floatval($log->Withdraw ?? 0);
|
||||
$totalDeposit += $deposit;
|
||||
$totalWithdraw += $withdraw;
|
||||
|
||||
// 거래일시 파싱
|
||||
$transDT = $log->TransDT ?? '';
|
||||
$transDate = '';
|
||||
$transTime = '';
|
||||
$dateTime = '';
|
||||
|
||||
if (!empty($transDT) && strlen($transDT) >= 14) {
|
||||
$transDate = substr($transDT, 0, 8);
|
||||
$transTime = substr($transDT, 8, 6);
|
||||
$dateTime = substr($transDT, 0, 4) . '-' . substr($transDT, 4, 2) . '-' . substr($transDT, 6, 2) . ' ' .
|
||||
substr($transDT, 8, 2) . ':' . substr($transDT, 10, 2) . ':' . substr($transDT, 12, 2);
|
||||
}
|
||||
|
||||
// 적요 파싱
|
||||
$summary = $log->TransRemark1 ?? $log->Summary ?? '';
|
||||
$remark2 = $log->TransRemark2 ?? '';
|
||||
$transType = $log->TransType ?? '';
|
||||
$fullSummary = $summary;
|
||||
if (!empty($remark2)) {
|
||||
$fullSummary = $fullSummary ? $fullSummary . ' ' . $remark2 : $remark2;
|
||||
}
|
||||
|
||||
$logs[] = [
|
||||
'transDate' => $transDate,
|
||||
'transTime' => $transTime,
|
||||
'transDateTime' => $dateTime,
|
||||
'bankAccountNum' => $log->BankAccountNum ?? '',
|
||||
'bankName' => $log->BankName ?? $defaultBankName,
|
||||
'deposit' => $deposit,
|
||||
'withdraw' => $withdraw,
|
||||
'depositFormatted' => number_format($deposit),
|
||||
'withdrawFormatted' => number_format($withdraw),
|
||||
'balance' => floatval($log->Balance ?? 0),
|
||||
'balanceFormatted' => number_format(floatval($log->Balance ?? 0)),
|
||||
'summary' => $fullSummary,
|
||||
'cast' => $log->Cast ?? '',
|
||||
'memo' => $log->Memo ?? '',
|
||||
'transOffice' => $log->TransOffice ?? ''
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'logs' => $logs,
|
||||
'summary' => [
|
||||
'totalDeposit' => $totalDeposit,
|
||||
'totalWithdraw' => $totalWithdraw,
|
||||
'count' => count($logs)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 코드 체크
|
||||
*/
|
||||
private function checkErrorCode($data): ?int
|
||||
{
|
||||
if (isset($data->CurrentPage) && is_numeric($data->CurrentPage) && $data->CurrentPage < 0) {
|
||||
return (int)$data->CurrentPage;
|
||||
}
|
||||
if (isset($data->BankAccountNum) && is_numeric($data->BankAccountNum) && $data->BankAccountNum < 0) {
|
||||
return (int)$data->BankAccountNum;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 메시지 반환
|
||||
*/
|
||||
private function getErrorMessage(int $errorCode): string
|
||||
{
|
||||
$messages = [
|
||||
-10002 => '인증 실패 (-10002). CERTKEY가 올바르지 않거나 만료되었습니다.',
|
||||
-50214 => '은행 로그인 실패 (-50214). 바로빌 사이트에서 계좌 비밀번호/인증서를 점검해주세요.',
|
||||
-24005 => '사용자 정보 불일치 (-24005). 사업자번호를 확인해주세요.',
|
||||
-25001 => '등록된 계좌가 없습니다 (-25001).',
|
||||
-25005 => '조회된 데이터가 없습니다 (-25005).',
|
||||
-25006 => '계좌번호가 잘못되었습니다 (-25006).',
|
||||
-25007 => '조회 기간이 잘못되었습니다 (-25007).',
|
||||
];
|
||||
return $messages[$errorCode] ?? '바로빌 API 오류: ' . $errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 은행 코드 -> 은행명 변환
|
||||
*/
|
||||
private function getBankName(string $code): string
|
||||
{
|
||||
$banks = [
|
||||
'002' => 'KDB산업은행',
|
||||
'003' => 'IBK기업은행',
|
||||
'004' => 'KB국민은행',
|
||||
'007' => '수협은행',
|
||||
'011' => 'NH농협은행',
|
||||
'020' => '우리은행',
|
||||
'023' => 'SC제일은행',
|
||||
'027' => '한국씨티은행',
|
||||
'031' => '대구은행',
|
||||
'032' => '부산은행',
|
||||
'034' => '광주은행',
|
||||
'035' => '제주은행',
|
||||
'037' => '전북은행',
|
||||
'039' => '경남은행',
|
||||
'045' => '새마을금고',
|
||||
'048' => '신협',
|
||||
'050' => '저축은행',
|
||||
'064' => '산림조합',
|
||||
'071' => '우체국',
|
||||
'081' => '하나은행',
|
||||
'088' => '신한은행',
|
||||
'089' => 'K뱅크',
|
||||
'090' => '카카오뱅크',
|
||||
'092' => '토스뱅크'
|
||||
];
|
||||
return $banks[$code] ?? $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* SOAP 호출
|
||||
*/
|
||||
private function callSoap(string $method, array $params = []): array
|
||||
{
|
||||
if (!$this->soapClient) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => '바로빌 SOAP 클라이언트가 초기화되지 않았습니다.'
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($this->certKey) && !$this->isTestMode) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'CERTKEY가 설정되지 않았습니다.'
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($this->corpNum)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => '사업자번호가 설정되지 않았습니다.'
|
||||
];
|
||||
}
|
||||
|
||||
// CERTKEY와 CorpNum 자동 추가
|
||||
if (!isset($params['CERTKEY'])) {
|
||||
$params['CERTKEY'] = $this->certKey ?? '';
|
||||
}
|
||||
if (!isset($params['CorpNum'])) {
|
||||
$params['CorpNum'] = $this->corpNum;
|
||||
}
|
||||
|
||||
try {
|
||||
Log::info("바로빌 계좌 API 호출 - Method: {$method}, CorpNum: {$this->corpNum}");
|
||||
|
||||
$result = $this->soapClient->$method($params);
|
||||
$resultProperty = $method . 'Result';
|
||||
|
||||
if (isset($result->$resultProperty)) {
|
||||
$resultData = $result->$resultProperty;
|
||||
|
||||
// 에러 코드 체크
|
||||
if (is_numeric($resultData) && $resultData < 0) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $this->getErrorMessage((int)$resultData),
|
||||
'error_code' => (int)$resultData
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $resultData
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $result
|
||||
];
|
||||
} catch (\SoapFault $e) {
|
||||
Log::error('바로빌 SOAP 오류: ' . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'SOAP 오류: ' . $e->getMessage(),
|
||||
'error_code' => $e->getCode()
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('바로빌 API 호출 오류: ' . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'API 호출 오류: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,11 +584,11 @@ protected function seedMainMenus(): void
|
||||
]);
|
||||
$this->createMenu([
|
||||
'parent_id' => $barobillGroup->id,
|
||||
'name' => '계좌조회',
|
||||
'url' => '/barobill/bank-account',
|
||||
'name' => '계좌 입출금내역',
|
||||
'url' => '/barobill/eaccount',
|
||||
'icon' => 'credit-card',
|
||||
'sort_order' => $barobillSubOrder++,
|
||||
'options' => ['route_name' => 'barobill.bank-account.index', 'section' => 'main'],
|
||||
'options' => ['route_name' => 'barobill.eaccount.index', 'section' => 'main'],
|
||||
]);
|
||||
$this->createMenu([
|
||||
'parent_id' => $barobillGroup->id,
|
||||
|
||||
476
resources/views/barobill/eaccount/index.blade.php
Normal file
476
resources/views/barobill/eaccount/index.blade.php
Normal file
@@ -0,0 +1,476 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '계좌 입출금내역')
|
||||
|
||||
@section('content')
|
||||
<!-- 현재 테넌트 정보 카드 (React 외부) -->
|
||||
@if($currentTenant)
|
||||
<div class="rounded-xl shadow-lg p-5 mb-6" style="background: linear-gradient(to right, #059669, #0d9488); color: white;">
|
||||
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="p-3 rounded-xl" style="background: rgba(255,255,255,0.2);">
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="px-2 py-0.5 rounded-full text-xs font-bold" style="background: rgba(255,255,255,0.2);">T-ID: {{ $currentTenant->id }}</span>
|
||||
@if($currentTenant->id == 1)
|
||||
<span class="px-2 py-0.5 rounded-full text-xs font-bold" style="background: #facc15; color: #713f12;">파트너사</span>
|
||||
@endif
|
||||
</div>
|
||||
<h2 class="text-xl font-bold">{{ $currentTenant->company_name }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
@if($barobillMember)
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3 text-sm">
|
||||
<div class="rounded-lg p-2" style="background: rgba(255,255,255,0.1);">
|
||||
<p class="text-xs" style="color: rgba(255,255,255,0.6);">사업자번호</p>
|
||||
<p class="font-medium">{{ $barobillMember->biz_no }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg p-2" style="background: rgba(255,255,255,0.1);">
|
||||
<p class="text-xs" style="color: rgba(255,255,255,0.6);">대표자</p>
|
||||
<p class="font-medium">{{ $barobillMember->ceo_name ?? '-' }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg p-2" style="background: rgba(255,255,255,0.1);">
|
||||
<p class="text-xs" style="color: rgba(255,255,255,0.6);">담당자</p>
|
||||
<p class="font-medium">{{ $barobillMember->manager_name ?? '-' }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg p-2" style="background: rgba(255,255,255,0.1);">
|
||||
<p class="text-xs" style="color: rgba(255,255,255,0.6);">바로빌 ID</p>
|
||||
<p class="font-medium">{{ $barobillMember->barobill_id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center gap-2" style="color: #fef08a;">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span class="text-sm">바로빌 회원사 미연동</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div id="eaccount-root"></div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
|
||||
<script type="text/babel">
|
||||
const { useState, useEffect, useRef } = React;
|
||||
|
||||
// API Routes
|
||||
const API = {
|
||||
accounts: '{{ route("barobill.eaccount.accounts") }}',
|
||||
transactions: '{{ route("barobill.eaccount.transactions") }}',
|
||||
};
|
||||
|
||||
const CSRF_TOKEN = '{{ csrf_token() }}';
|
||||
|
||||
// 날짜 유틸리티 함수
|
||||
const getMonthDates = (offset = 0) => {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth() + offset;
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
return {
|
||||
from: firstDay.toISOString().split('T')[0],
|
||||
to: lastDay.toISOString().split('T')[0]
|
||||
};
|
||||
};
|
||||
|
||||
// StatCard Component
|
||||
const StatCard = ({ title, value, subtext, icon, color = 'blue' }) => {
|
||||
const colorClasses = {
|
||||
blue: 'bg-blue-50 text-blue-600',
|
||||
green: 'bg-green-50 text-green-600',
|
||||
red: 'bg-red-50 text-red-600',
|
||||
stone: 'bg-stone-50 text-stone-600'
|
||||
};
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-6 shadow-sm border border-stone-100 hover:shadow-md transition-shadow">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<h3 className="text-sm font-medium text-stone-500">{title}</h3>
|
||||
<div className={`p-2 rounded-lg ${colorClasses[color] || colorClasses.blue}`}>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-stone-900 mb-1">{value}</div>
|
||||
{subtext && <div className="text-xs text-stone-400">{subtext}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// AccountSelector Component
|
||||
const AccountSelector = ({ accounts, selectedAccount, onSelect }) => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => onSelect('')}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
selectedAccount === ''
|
||||
? 'bg-emerald-600 text-white'
|
||||
: 'bg-white border border-stone-200 text-stone-700 hover:bg-stone-50'
|
||||
}`}
|
||||
>
|
||||
전체 계좌
|
||||
</button>
|
||||
{accounts.map(acc => (
|
||||
<button
|
||||
key={acc.bankAccountNum}
|
||||
onClick={() => onSelect(acc.bankAccountNum)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
selectedAccount === acc.bankAccountNum
|
||||
? 'bg-emerald-600 text-white'
|
||||
: 'bg-white border border-stone-200 text-stone-700 hover:bg-stone-50'
|
||||
}`}
|
||||
>
|
||||
{acc.bankName} {acc.bankAccountNum ? '****' + acc.bankAccountNum.slice(-4) : ''}
|
||||
{acc.accountName && ` (${acc.accountName})`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
// TransactionTable Component
|
||||
const TransactionTable = ({ logs, loading, dateFrom, dateTo, onDateFromChange, onDateToChange, onThisMonth, onLastMonth, totalCount }) => {
|
||||
const formatCurrency = (val) => new Intl.NumberFormat('ko-KR').format(val || 0) + '원';
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-stone-100 overflow-hidden">
|
||||
<div className="p-6 border-b border-stone-100">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||||
<h2 className="text-lg font-bold text-stone-900">입출금 내역</h2>
|
||||
{/* 기간 조회 필터 */}
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm text-stone-500">기간</label>
|
||||
<input
|
||||
type="date"
|
||||
value={dateFrom}
|
||||
onChange={(e) => onDateFromChange(e.target.value)}
|
||||
className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none"
|
||||
/>
|
||||
<span className="text-stone-400">~</span>
|
||||
<input
|
||||
type="date"
|
||||
value={dateTo}
|
||||
onChange={(e) => onDateToChange(e.target.value)}
|
||||
className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={onThisMonth}
|
||||
className="px-3 py-1.5 text-sm bg-emerald-50 text-emerald-600 rounded-lg hover:bg-emerald-100 transition-colors font-medium"
|
||||
>
|
||||
이번 달
|
||||
</button>
|
||||
<button
|
||||
onClick={onLastMonth}
|
||||
className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium"
|
||||
>
|
||||
지난달
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-sm text-stone-500 ml-2">
|
||||
조회: <span className="font-semibold text-stone-700">{logs.length}</span>건
|
||||
{totalCount !== logs.length && (
|
||||
<span className="text-stone-400"> / 전체 {totalCount}건</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto" style={ {maxHeight: '500px', overflowY: 'auto'} }>
|
||||
<table className="w-full text-left text-sm text-stone-600">
|
||||
<thead className="bg-stone-50 text-xs uppercase font-medium text-stone-500 sticky top-0">
|
||||
<tr>
|
||||
<th className="px-4 py-4 bg-stone-50">거래일시</th>
|
||||
<th className="px-4 py-4 bg-stone-50">계좌정보</th>
|
||||
<th className="px-4 py-4 bg-stone-50">적요/내용</th>
|
||||
<th className="px-4 py-4 text-right bg-stone-50 text-blue-600">입금</th>
|
||||
<th className="px-4 py-4 text-right bg-stone-50 text-red-600">출금</th>
|
||||
<th className="px-4 py-4 text-right bg-stone-50">잔액</th>
|
||||
<th className="px-4 py-4 bg-stone-50">상대방</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-stone-100">
|
||||
{logs.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="7" className="px-6 py-8 text-center text-stone-400">
|
||||
해당 기간에 조회된 입출금 내역이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
logs.map((log, index) => (
|
||||
<tr key={index} className="hover:bg-stone-50 transition-colors">
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="font-medium text-stone-900">{log.transDateTime || '-'}</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="font-medium text-stone-900">{log.bankName}</div>
|
||||
<div className="text-xs text-stone-400 font-mono">
|
||||
{log.bankAccountNum ? '****' + log.bankAccountNum.slice(-4) : '-'}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="font-medium text-stone-900">{log.summary || '-'}</div>
|
||||
{log.memo && <div className="text-xs text-stone-400">{log.memo}</div>}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right font-medium text-blue-600">
|
||||
{log.deposit > 0 ? log.depositFormatted + '원' : '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right font-medium text-red-600">
|
||||
{log.withdraw > 0 ? log.withdrawFormatted + '원' : '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-stone-700">
|
||||
{log.balanceFormatted}원
|
||||
</td>
|
||||
<td className="px-4 py-3 text-stone-500">
|
||||
{log.cast || '-'}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Main App Component
|
||||
const App = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [accounts, setAccounts] = useState([]);
|
||||
const [selectedAccount, setSelectedAccount] = useState('');
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [summary, setSummary] = useState({});
|
||||
const [pagination, setPagination] = useState({});
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// 날짜 필터 상태 (기본: 현재 월)
|
||||
const currentMonth = getMonthDates(0);
|
||||
const [dateFrom, setDateFrom] = useState(currentMonth.from);
|
||||
const [dateTo, setDateTo] = useState(currentMonth.to);
|
||||
|
||||
// 초기 로드
|
||||
useEffect(() => {
|
||||
loadAccounts();
|
||||
}, []);
|
||||
|
||||
// 날짜 또는 계좌 변경 시 거래내역 로드
|
||||
useEffect(() => {
|
||||
if (dateFrom && dateTo) {
|
||||
loadTransactions();
|
||||
}
|
||||
}, [dateFrom, dateTo, selectedAccount]);
|
||||
|
||||
const loadAccounts = async () => {
|
||||
try {
|
||||
const response = await fetch(API.accounts);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setAccounts(data.accounts || []);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('계좌 목록 로드 오류:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const loadTransactions = async (page = 1) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
startDate: dateFrom.replace(/-/g, ''),
|
||||
endDate: dateTo.replace(/-/g, ''),
|
||||
accountNum: selectedAccount,
|
||||
page: page,
|
||||
limit: 50
|
||||
});
|
||||
|
||||
const response = await fetch(`${API.transactions}?${params}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setLogs(data.data?.logs || []);
|
||||
setPagination(data.data?.pagination || {});
|
||||
setSummary(data.data?.summary || {});
|
||||
} else {
|
||||
setError(data.error || '조회 실패');
|
||||
setLogs([]);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('서버 통신 오류: ' + err.message);
|
||||
setLogs([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 이번 달 버튼
|
||||
const handleThisMonth = () => {
|
||||
const dates = getMonthDates(0);
|
||||
setDateFrom(dates.from);
|
||||
setDateTo(dates.to);
|
||||
};
|
||||
|
||||
// 지난달 버튼
|
||||
const handleLastMonth = () => {
|
||||
const dates = getMonthDates(-1);
|
||||
setDateFrom(dates.from);
|
||||
setDateTo(dates.to);
|
||||
};
|
||||
|
||||
const formatCurrency = (val) => new Intl.NumberFormat('ko-KR').format(val || 0) + '원';
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Page Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-stone-900">계좌 입출금내역</h1>
|
||||
<p className="text-stone-500 mt-1">바로빌 API를 통한 계좌 입출금내역 조회</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@if($isTestMode)
|
||||
<span className="px-3 py-1 bg-amber-100 text-amber-700 rounded-full text-xs font-medium">테스트 모드</span>
|
||||
@endif
|
||||
@if($hasSoapClient)
|
||||
<span className="px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs font-medium">SOAP 연결됨</span>
|
||||
@else
|
||||
<span className="px-3 py-1 bg-red-100 text-red-700 rounded-full text-xs font-medium">SOAP 미연결</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dashboard */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<StatCard
|
||||
title="총 입금액"
|
||||
value={formatCurrency(summary.totalDeposit)}
|
||||
subtext="조회기간 합계"
|
||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4"/></svg>}
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
title="총 출금액"
|
||||
value={formatCurrency(summary.totalWithdraw)}
|
||||
subtext="조회기간 합계"
|
||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M20 12H4"/></svg>}
|
||||
color="red"
|
||||
/>
|
||||
<StatCard
|
||||
title="등록된 계좌"
|
||||
value={`${accounts.length}개`}
|
||||
subtext="사용 가능한 계좌"
|
||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>}
|
||||
color="green"
|
||||
/>
|
||||
<StatCard
|
||||
title="거래건수"
|
||||
value={`${(summary.count || 0).toLocaleString()}건`}
|
||||
subtext="전체 입출금 건수"
|
||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>}
|
||||
color="stone"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Account Filter */}
|
||||
{accounts.length > 0 && (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-stone-100 p-6">
|
||||
<h2 className="text-sm font-medium text-stone-700 mb-3">계좌 선택</h2>
|
||||
<AccountSelector
|
||||
accounts={accounts}
|
||||
selectedAccount={selectedAccount}
|
||||
onSelect={setSelectedAccount}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error Display */}
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-600 p-4 rounded-xl">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-xl">⚠️</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold mb-2">{error}</p>
|
||||
{error.includes('-50214') && (
|
||||
<div className="mt-3 p-3 bg-white rounded border border-red-200 text-sm">
|
||||
<p className="font-medium mb-2">해결 방법:</p>
|
||||
<ol className="list-decimal list-inside space-y-1 text-stone-700">
|
||||
<li>바로빌 사이트(<a href="https://www.barobill.co.kr" target="_blank" className="text-blue-600 hover:underline">https://www.barobill.co.kr</a>)에 로그인</li>
|
||||
<li>계좌 관리 메뉴에서 해당 계좌 확인</li>
|
||||
<li>계좌 비밀번호가 변경되었는지 확인</li>
|
||||
<li>인증서가 만료되지 않았는지 확인</li>
|
||||
<li>필요시 계좌 재등록 또는 비밀번호 재설정</li>
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Transaction Table */}
|
||||
{!error && (
|
||||
<TransactionTable
|
||||
logs={logs}
|
||||
loading={loading}
|
||||
dateFrom={dateFrom}
|
||||
dateTo={dateTo}
|
||||
onDateFromChange={setDateFrom}
|
||||
onDateToChange={setDateTo}
|
||||
onThisMonth={handleThisMonth}
|
||||
onLastMonth={handleLastMonth}
|
||||
totalCount={summary.count || logs.length}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{!error && pagination.maxPageNum > 1 && (
|
||||
<div className="flex justify-center gap-2">
|
||||
<button
|
||||
onClick={() => loadTransactions(Math.max(1, pagination.currentPage - 1))}
|
||||
disabled={pagination.currentPage === 1}
|
||||
className="px-3 py-1 rounded bg-white border disabled:opacity-50"
|
||||
>
|
||||
이전
|
||||
</button>
|
||||
<span className="px-3 py-1">
|
||||
{pagination.currentPage} / {pagination.maxPageNum}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => loadTransactions(Math.min(pagination.maxPageNum, pagination.currentPage + 1))}
|
||||
disabled={pagination.currentPage === pagination.maxPageNum}
|
||||
className="px-3 py-1 rounded bg-white border disabled:opacity-50"
|
||||
>
|
||||
다음
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('eaccount-root'));
|
||||
root.render(<App />);
|
||||
</script>
|
||||
@endpush
|
||||
@@ -286,6 +286,13 @@
|
||||
Route::post('/send-to-nts', [\App\Http\Controllers\Barobill\EtaxController::class, 'sendToNts'])->name('send-to-nts');
|
||||
Route::post('/delete', [\App\Http\Controllers\Barobill\EtaxController::class, 'delete'])->name('delete');
|
||||
});
|
||||
|
||||
// 계좌 입출금내역 (React 페이지)
|
||||
Route::prefix('eaccount')->name('eaccount.')->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Barobill\EaccountController::class, 'index'])->name('index');
|
||||
Route::get('/accounts', [\App\Http\Controllers\Barobill\EaccountController::class, 'accounts'])->name('accounts');
|
||||
Route::get('/transactions', [\App\Http\Controllers\Barobill\EaccountController::class, 'transactions'])->name('transactions');
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user