319 lines
18 KiB
PHP
319 lines
18 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '일일자금일보')
|
|
|
|
@push('styles')
|
|
<style>
|
|
@media print {
|
|
.no-print { display: none !important; }
|
|
body { background: white !important; }
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<div id="daily-fund-root"></div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
@include('partials.react-cdn')
|
|
<script type="text/babel">
|
|
const { useState, useEffect } = React;
|
|
|
|
// 로컬 시간대 기준 날짜 문자열 (YYYY-MM-DD)
|
|
const toLocalDateString = (date) => {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
function DailyFundReport() {
|
|
const today = new Date();
|
|
const monthAgo = new Date();
|
|
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
|
|
|
const [startDate, setStartDate] = useState(toLocalDateString(monthAgo));
|
|
const [endDate, setEndDate] = useState(toLocalDateString(today));
|
|
const [dailyReports, setDailyReports] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const fetchData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await fetch(`/finance/daily-fund/period-report?start_date=${startDate.replace(/-/g, '')}&end_date=${endDate.replace(/-/g, '')}`);
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
setDailyReports(data.data.dailyReports || []);
|
|
}
|
|
} catch (err) {
|
|
console.error('데이터 조회 실패:', err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => { fetchData(); }, [startDate, endDate]);
|
|
|
|
const formatCurrency = (num) => {
|
|
if (!num) return '0';
|
|
return Number(num).toLocaleString();
|
|
};
|
|
|
|
const setThisMonth = () => {
|
|
const now = new Date();
|
|
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
setStartDate(toLocalDateString(firstDay));
|
|
setEndDate(toLocalDateString(now));
|
|
};
|
|
|
|
const setLastMonth = () => {
|
|
const now = new Date();
|
|
const firstDay = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
const lastDay = new Date(now.getFullYear(), now.getMonth(), 0);
|
|
setStartDate(toLocalDateString(firstDay));
|
|
setEndDate(toLocalDateString(lastDay));
|
|
};
|
|
|
|
const setMonthOffset = (offset) => {
|
|
const now = new Date();
|
|
const firstDay = new Date(now.getFullYear(), now.getMonth() + offset, 1);
|
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + offset + 1, 0);
|
|
setStartDate(toLocalDateString(firstDay));
|
|
setEndDate(toLocalDateString(lastDay));
|
|
};
|
|
|
|
const handlePrint = () => window.print();
|
|
|
|
return (
|
|
<div className="px-4 py-6">
|
|
{/* 헤더 */}
|
|
<div className="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 mb-6 no-print">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-800">일일자금일보</h1>
|
|
<p className="text-sm text-gray-500 mt-1">Daily Fund Report</p>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<button onClick={handlePrint} className="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg text-sm">
|
|
인쇄
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 기간 선택 */}
|
|
<div className="bg-white rounded-lg shadow-sm p-4 mb-6 no-print">
|
|
<div className="flex flex-wrap items-center gap-4">
|
|
<span className="text-sm font-medium text-gray-600">기간</span>
|
|
<input
|
|
type="date"
|
|
value={startDate}
|
|
onChange={(e) => setStartDate(e.target.value)}
|
|
className="px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
|
/>
|
|
<span className="text-gray-400">~</span>
|
|
<input
|
|
type="date"
|
|
value={endDate}
|
|
onChange={(e) => setEndDate(e.target.value)}
|
|
className="px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
|
/>
|
|
<button onClick={setThisMonth} className="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm">
|
|
이번 달
|
|
</button>
|
|
<button onClick={setLastMonth} className="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm">
|
|
지난 달
|
|
</button>
|
|
<button onClick={() => setMonthOffset(-2)} className="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm">
|
|
D-2월
|
|
</button>
|
|
<button onClick={() => setMonthOffset(-3)} className="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm">
|
|
D-3월
|
|
</button>
|
|
<button onClick={() => setMonthOffset(-4)} className="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm">
|
|
D-4월
|
|
</button>
|
|
<button onClick={() => setMonthOffset(-5)} className="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm">
|
|
D-5월
|
|
</button>
|
|
<button onClick={fetchData} className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm">
|
|
조회
|
|
</button>
|
|
<span className="text-sm text-gray-500 ml-auto">조회: {dailyReports.length}일</span>
|
|
</div>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="bg-white rounded-lg shadow-sm p-12 text-center">
|
|
<div className="flex items-center justify-center gap-2 text-gray-400">
|
|
<svg className="animate-spin h-5 w-5" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
불러오는 중...
|
|
</div>
|
|
</div>
|
|
) : dailyReports.length === 0 ? (
|
|
<div className="bg-white rounded-lg shadow-sm p-12 text-center text-gray-400">
|
|
해당 기간에 거래내역이 없습니다.
|
|
</div>
|
|
) : (
|
|
<div className="space-y-6">
|
|
{dailyReports.map((report, idx) => (
|
|
<div key={report.date} className="bg-white rounded-lg shadow-sm overflow-hidden">
|
|
{/* 일자 헤더 */}
|
|
<div className="bg-gradient-to-r from-blue-600 to-blue-700 px-6 py-4">
|
|
<h2 className="text-lg font-bold text-white">
|
|
일자: {report.dateFormatted}
|
|
</h2>
|
|
</div>
|
|
|
|
{/* 계좌별 잔고 테이블 */}
|
|
<div className="p-6 border-b border-gray-200">
|
|
<table className="w-full border-collapse">
|
|
<thead>
|
|
<tr className="bg-gray-50">
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-gray-700 border">구분</th>
|
|
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-700 border">입금</th>
|
|
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-700 border">출금</th>
|
|
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-700 border">잔액</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{report.accounts.map((acc, accIdx) => (
|
|
<tr key={accIdx} className="hover:bg-gray-50">
|
|
<td className="px-4 py-3 text-sm text-gray-800 border">
|
|
<span className="font-medium">{acc.bankName}</span>
|
|
<span className="text-gray-500 ml-2 text-xs">({acc.accountNum})</span>
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-right text-blue-600 border">
|
|
{acc.deposit > 0 ? formatCurrency(acc.deposit) : ''}
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-right text-red-600 border">
|
|
{acc.withdraw > 0 ? formatCurrency(acc.withdraw) : ''}
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-right font-semibold text-gray-800 border">
|
|
{formatCurrency(acc.balance)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
<tr className="bg-gray-100 font-semibold">
|
|
<td className="px-4 py-3 text-sm text-gray-800 border">합계</td>
|
|
<td className="px-4 py-3 text-sm text-right text-blue-600 border">
|
|
{formatCurrency(report.totalDeposit)}
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-right text-red-600 border">
|
|
{formatCurrency(report.totalWithdraw)}
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-right text-gray-800 border">
|
|
{formatCurrency(report.accounts.reduce((sum, a) => sum + Number(a.balance || 0), 0))}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* 입출금 내역 */}
|
|
<div className="p-6">
|
|
<h3 className="text-base font-semibold text-gray-800 mb-4 text-center bg-blue-50 py-2 rounded">
|
|
<예금 입출금 내역>
|
|
</h3>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* 입금 내역 */}
|
|
<div>
|
|
<table className="w-full border-collapse">
|
|
<thead>
|
|
<tr className="bg-blue-50">
|
|
<th colSpan="2" className="px-4 py-2 text-center text-sm font-semibold text-blue-700 border">
|
|
입금
|
|
</th>
|
|
</tr>
|
|
<tr className="bg-gray-50">
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-600 border">입금처/적요</th>
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-600 border w-28">금액</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{report.deposits.length === 0 ? (
|
|
<tr><td colSpan="2" className="px-3 py-4 text-center text-gray-400 text-sm border">입금 내역 없음</td></tr>
|
|
) : (
|
|
<>
|
|
{report.deposits.map((d, dIdx) => (
|
|
<tr key={dIdx} className="hover:bg-gray-50">
|
|
<td className="px-3 py-2 text-sm text-gray-800 border">
|
|
<div className="font-medium">{d.cast}{d.summary ? ` - ${d.summary}` : ''}</div>
|
|
<div className="text-xs text-gray-500">{d.bankName}</div>
|
|
</td>
|
|
<td className="px-3 py-2 text-sm text-right text-blue-600 font-medium border">
|
|
{formatCurrency(d.amount)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
<tr className="bg-blue-50 font-semibold">
|
|
<td className="px-3 py-2 text-sm text-gray-800 border">입금 합계</td>
|
|
<td className="px-3 py-2 text-sm text-right text-blue-700 border">
|
|
{formatCurrency(report.totalDeposit)}
|
|
</td>
|
|
</tr>
|
|
</>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* 출금 내역 */}
|
|
<div>
|
|
<table className="w-full border-collapse">
|
|
<thead>
|
|
<tr className="bg-red-50">
|
|
<th colSpan="2" className="px-4 py-2 text-center text-sm font-semibold text-red-700 border">
|
|
출금
|
|
</th>
|
|
</tr>
|
|
<tr className="bg-gray-50">
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-600 border">출금처/적요</th>
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-600 border w-28">금액</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{report.withdrawals.length === 0 ? (
|
|
<tr><td colSpan="2" className="px-3 py-4 text-center text-gray-400 text-sm border">출금 내역 없음</td></tr>
|
|
) : (
|
|
<>
|
|
{report.withdrawals.map((w, wIdx) => (
|
|
<tr key={wIdx} className="hover:bg-gray-50">
|
|
<td className="px-3 py-2 text-sm text-gray-800 border">
|
|
<div className="font-medium">{w.cast}{w.summary ? ` - ${w.summary}` : ''}</div>
|
|
<div className="text-xs text-gray-500">{w.bankName}</div>
|
|
</td>
|
|
<td className="px-3 py-2 text-sm text-right text-red-600 font-medium border">
|
|
{formatCurrency(w.amount)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
<tr className="bg-red-50 font-semibold">
|
|
<td className="px-3 py-2 text-sm text-gray-800 border">출금 합계</td>
|
|
<td className="px-3 py-2 text-sm text-right text-red-700 border">
|
|
{formatCurrency(report.totalWithdraw)}
|
|
</td>
|
|
</tr>
|
|
</>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const rootElement = document.getElementById('daily-fund-root');
|
|
if (rootElement) { ReactDOM.createRoot(rootElement).render(<DailyFundReport />); }
|
|
</script>
|
|
@endpush
|