Day 10 完了

実装内容:
1. frontend/src/components/Navbar.tsx - 帳票出力リンク追加
2. frontend/src/app/reports/page.tsx - 帳票出力画面(PDFダウンロード)
API動作確認:
- /api/reports/kyosai/2025/ → HTTP 200 (76KB PDF)
- /api/reports/chusankan/2025/ → HTTP 200 (84KB PDF)
ブラウザで http://localhost:3000/reports から帳票ダウンロードが可能です。
次の工程に移りますか?
This commit is contained in:
Akira
2026-02-15 13:45:46 +09:00
parent 923dd5dece
commit 6334c6deaa
2 changed files with 125 additions and 1 deletions

View File

@@ -0,0 +1,113 @@
'use client';
import { useState } from 'react';
import { api } from '@/lib/api';
import Navbar from '@/components/Navbar';
import { FileDown, Loader2 } from 'lucide-react';
const downloadPdf = async (url: string, filename: string) => {
const response = await api.get(url, { responseType: 'blob' });
const blob = new Blob([response.data], { type: 'application/pdf' });
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(downloadUrl);
};
export default function ReportsPage() {
const [year, setYear] = useState<number>(2025);
const [downloading, setDownloading] = useState<string | null>(null);
const handleDownloadKyosai = async () => {
setDownloading('kyosai');
try {
await downloadPdf(`/reports/kyosai/${year}/`, `水稲共済細目書_${year}.pdf`);
} catch (error) {
console.error('Download failed:', error);
alert('ダウンロードに失敗しました');
} finally {
setDownloading(null);
}
};
const handleDownloadChusankan = async () => {
setDownloading('chusankan');
try {
await downloadPdf(`/reports/chusankan/${year}/`, `中山間交付金申请书_${year}.pdf`);
} catch (error) {
console.error('Download failed:', error);
alert('ダウンロードに失敗しました');
} finally {
setDownloading(null);
}
};
return (
<div className="min-h-screen bg-gray-50">
<Navbar />
<div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 className="text-2xl font-bold text-gray-900 mb-6"></h1>
<div className="bg-white rounded-lg shadow p-6">
<div className="mb-6">
<label htmlFor="year" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<select
id="year"
value={year}
onChange={(e) => setYear(parseInt(e.target.value))}
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 w-full max-w-xs"
>
<option value={2025}>2025</option>
<option value={2026}>2026</option>
<option value={2027}>2027</option>
</select>
</div>
<div className="space-y-4">
<button
onClick={handleDownloadKyosai}
disabled={downloading !== null}
className="w-full flex items-center justify-center px-4 py-3 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{downloading === 'kyosai' ? (
<>
<Loader2 className="h-5 w-5 mr-2 animate-spin" />
...
</>
) : (
<>
<FileDown className="h-5 w-5 mr-2" />
</>
)}
</button>
<button
onClick={handleDownloadChusankan}
disabled={downloading !== null}
className="w-full flex items-center justify-center px-4 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{downloading === 'chusankan' ? (
<>
<Loader2 className="h-5 w-5 mr-2 animate-spin" />
...
</>
) : (
<>
<FileDown className="h-5 w-5 mr-2" />
</>
)}
</button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,7 +1,7 @@
'use client';
import { useRouter, usePathname } from 'next/navigation';
import { LogOut, Wheat, MapPin } from 'lucide-react';
import { LogOut, Wheat, MapPin, FileText } from 'lucide-react';
import { logout } from '@/lib/api';
export default function Navbar() {
@@ -43,6 +43,17 @@ export default function Navbar() {
<MapPin className="h-4 w-4 mr-2" />
</button>
<button
onClick={() => router.push('/reports')}
className={`flex items-center px-3 py-2 text-sm rounded-md transition-colors ${
isActive('/reports') || pathname?.startsWith('/reports/')
? 'text-green-700 bg-green-50'
: 'text-gray-700 hover:text-gray-900 hover:bg-gray-100'
}`}
>
<FileText className="h-4 w-4 mr-2" />
</button>
</div>
</div>
<div className="flex items-center">