Files
sam-manage/resources/views/finance/daily-fund.blade.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">
&lt;예금 입출금 내역&gt;
</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