feat:재무 대시보드 계좌별 잔액 바로빌 실시간 연동
- 페이지 로드 시 바로빌 API로 실시간 잔액 조회 - 새로고침 버튼으로 수동 조회 가능 - 총 잔액 카드도 실시간 업데이트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,7 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">총 잔액</p>
|
||||
<p class="text-2xl font-bold text-gray-800 mt-1">{{ number_format($accountSummary['total_balance']) }}원</p>
|
||||
<p class="text-2xl font-bold text-gray-800 mt-1" data-total-balance>{{ number_format($accountSummary['total_balance']) }}원</p>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ $accountSummary['total_accounts'] }}개 계좌</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
@@ -130,13 +130,19 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-800">계좌별 잔액</h2>
|
||||
<a href="{{ route('finance.accounts.index') }}" class="text-sm text-blue-600 hover:text-blue-700">
|
||||
전체보기
|
||||
</a>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" onclick="refreshAccountBalances()" id="refreshBalanceBtn" class="text-xs text-gray-500 hover:text-blue-600 flex items-center gap-1" title="잔액 새로고침">
|
||||
<svg id="refreshBalanceIcon" class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
<span id="refreshBalanceText">새로고침</span>
|
||||
</button>
|
||||
<a href="{{ route('finance.accounts.index') }}" class="text-sm text-blue-600 hover:text-blue-700">
|
||||
전체보기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-100 max-h-80 overflow-y-auto">
|
||||
<div id="accountBalancesList" class="divide-y divide-gray-100 max-h-80 overflow-y-auto">
|
||||
@forelse($accountBalances as $account)
|
||||
<div class="px-6 py-3 flex items-center justify-between hover:bg-gray-50">
|
||||
<div class="px-6 py-3 flex items-center justify-between hover:bg-gray-50" data-account-number="{{ str_replace('-', '', $account->account_number) }}">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
|
||||
<span class="text-xs font-medium text-gray-600">{{ mb_substr($account->bank_name, 0, 2) }}</span>
|
||||
@@ -149,7 +155,7 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm font-semibold text-gray-800">{{ number_format($account->balance) }}원</p>
|
||||
<p class="text-sm font-semibold text-gray-800 account-balance" data-original="{{ $account->balance }}">{{ number_format($account->balance) }}원</p>
|
||||
<p class="text-xs text-gray-400">{{ $account->account_type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -299,4 +305,69 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function refreshAccountBalances() {
|
||||
const btn = document.getElementById('refreshBalanceBtn');
|
||||
const icon = document.getElementById('refreshBalanceIcon');
|
||||
const text = document.getElementById('refreshBalanceText');
|
||||
|
||||
// 로딩 상태
|
||||
btn.disabled = true;
|
||||
icon.classList.add('animate-spin');
|
||||
text.textContent = '조회중...';
|
||||
|
||||
try {
|
||||
const res = await fetch('{{ route("barobill.eaccount.accounts") }}');
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success && data.accounts) {
|
||||
// 계좌번호 -> 잔액 맵 생성
|
||||
const balanceMap = {};
|
||||
data.accounts.forEach(acc => {
|
||||
const num = (acc.bankAccountNum || '').replace(/-/g, '');
|
||||
balanceMap[num] = acc.balance || 0;
|
||||
});
|
||||
|
||||
// 각 계좌 잔액 업데이트
|
||||
let totalBalance = 0;
|
||||
document.querySelectorAll('#accountBalancesList [data-account-number]').forEach(row => {
|
||||
const accNum = row.dataset.accountNumber;
|
||||
const balanceEl = row.querySelector('.account-balance');
|
||||
if (balanceEl && balanceMap.hasOwnProperty(accNum)) {
|
||||
const newBalance = balanceMap[accNum];
|
||||
balanceEl.textContent = new Intl.NumberFormat('ko-KR').format(newBalance) + '원';
|
||||
balanceEl.classList.add('text-blue-600');
|
||||
setTimeout(() => balanceEl.classList.remove('text-blue-600'), 2000);
|
||||
totalBalance += newBalance;
|
||||
}
|
||||
});
|
||||
|
||||
// 총 잔액 업데이트
|
||||
const totalBalanceEl = document.querySelector('[data-total-balance]');
|
||||
if (totalBalanceEl) {
|
||||
totalBalanceEl.textContent = new Intl.NumberFormat('ko-KR').format(totalBalance) + '원';
|
||||
}
|
||||
|
||||
text.textContent = '완료';
|
||||
setTimeout(() => { text.textContent = '새로고침'; }, 2000);
|
||||
} else {
|
||||
text.textContent = '실패';
|
||||
setTimeout(() => { text.textContent = '새로고침'; }, 2000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('잔액 조회 오류:', e);
|
||||
text.textContent = '오류';
|
||||
setTimeout(() => { text.textContent = '새로고침'; }, 2000);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
icon.classList.remove('animate-spin');
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 로드 시 자동 조회
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
refreshAccountBalances();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user