A-6 完了。 本セッションの進捗まとめ:

タスク	内容	状態
A-3	前年度コピーボタン	 完了
A-4	品種のインライン追加・削除	 完了
A-5	PDFプレビュー機能	 完了
A-6	エクスポート機能	 完了
残りタスク:

A-2: チェックボックス・一括操作
A-1: ダッシュボード画面
A-7: 検索・フィルタ
確認ポイント:

作付け計画 (/allocation): 年度セレクタの横に「前年度コピー」「品種管理」ボタン、品種セレクトに「+ 新しい品種を追加...」
帳票出力 (/reports): 各帳票にプレビュー/ダウンロードの2ボタン
データ取込 (/import): ページ下部に「データエクスポート」(ZIPダウンロード)
This commit is contained in:
Akira
2026-02-19 12:21:17 +09:00
parent 23cb4d3118
commit 8b5e0fc66e
9 changed files with 497 additions and 128 deletions

View File

@@ -3,7 +3,7 @@
import { useState, useRef } from 'react';
import { api } from '@/lib/api';
import Navbar from '@/components/Navbar';
import { Upload, Loader2, CheckCircle, XCircle } from 'lucide-react';
import { Upload, Download, Loader2, CheckCircle, XCircle } from 'lucide-react';
interface ImportResult {
success: boolean;
@@ -20,10 +20,32 @@ export default function ImportPage() {
const [kyosaiResult, setKyosaiResult] = useState<ImportResult | null>(null);
const [yoshidaResult, setYoshidaResult] = useState<ImportResult | null>(null);
const [chusankanResult, setChusankanResult] = useState<ImportResult | null>(null);
const [exporting, setExporting] = useState(false);
const kyosaiInputRef = useRef<HTMLInputElement>(null);
const yoshidaInputRef = useRef<HTMLInputElement>(null);
const chusankanInputRef = useRef<HTMLInputElement>(null);
const handleExportZip = async () => {
setExporting(true);
try {
const response = await api.get('/fields/export/zip/', { responseType: 'blob' });
const blob = new Blob([response.data], { type: 'application/zip' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'keinasystem_backup.zip';
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Export failed:', error);
alert('エクスポートに失敗しました');
} finally {
setExporting(false);
}
};
const handleKyosaiUpload = async () => {
if (!kyosaiFile) {
alert('ファイルを選択してください');
@@ -399,6 +421,33 @@ export default function ImportPage() {
</div>
)}
</div>
{/* エクスポート */}
<div className="bg-white rounded-lg shadow p-6 border-t-4 border-gray-300">
<h2 className="text-lg font-semibold text-gray-900 mb-2">
</h2>
<p className="text-sm text-gray-600 mb-4">
CSV形式のZIPファイルとしてダウンロードします使
</p>
<button
onClick={handleExportZip}
disabled={exporting}
className="w-full flex items-center justify-center px-4 py-2 bg-gray-700 text-white rounded-md hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{exporting ? (
<>
<Loader2 className="h-5 w-5 mr-2 animate-spin" />
...
</>
) : (
<>
<Download className="h-5 w-5 mr-2" />
ZIPでダウンロード
</>
)}
</button>
</div>
</div>
</div>
</div>