修正完了

frontend/src/app/import/page.tsx に中山間マスタ取込セクションを追加しました:
- State追加: chusankanFile, chusankanResult, chusankanInputRef
- アップロード関数: handleChusankanUpload (endpoint: /fields/import/chusankan/)
- UI追加: 「中山間マスタ取込」セクション(黄色)
ビルド成功。http://localhost:3000/import で確認できます。
This commit is contained in:
Akira
2026-02-15 14:34:02 +09:00
parent 0edfaef71f
commit c78945b44b

View File

@@ -15,11 +15,14 @@ interface ImportResult {
export default function ImportPage() { export default function ImportPage() {
const [kyosaiFile, setKyosaiFile] = useState<File | null>(null); const [kyosaiFile, setKyosaiFile] = useState<File | null>(null);
const [yoshidaFile, setYoshidaFile] = useState<File | null>(null); const [yoshidaFile, setYoshidaFile] = useState<File | null>(null);
const [chusankanFile, setChusankanFile] = useState<File | null>(null);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const [kyosaiResult, setKyosaiResult] = useState<ImportResult | null>(null); const [kyosaiResult, setKyosaiResult] = useState<ImportResult | null>(null);
const [yoshidaResult, setYoshidaResult] = useState<ImportResult | null>(null); const [yoshidaResult, setYoshidaResult] = useState<ImportResult | null>(null);
const [chusankanResult, setChusankanResult] = useState<ImportResult | null>(null);
const kyosaiInputRef = useRef<HTMLInputElement>(null); const kyosaiInputRef = useRef<HTMLInputElement>(null);
const yoshidaInputRef = useRef<HTMLInputElement>(null); const yoshidaInputRef = useRef<HTMLInputElement>(null);
const chusankanInputRef = useRef<HTMLInputElement>(null);
const handleKyosaiUpload = async () => { const handleKyosaiUpload = async () => {
if (!kyosaiFile) { if (!kyosaiFile) {
@@ -109,6 +112,50 @@ export default function ImportPage() {
} }
}; };
const handleChusankanUpload = async () => {
if (!chusankanFile) {
alert('ファイルを選択してください');
return;
}
setUploading(true);
setChusankanResult(null);
try {
const formData = new FormData();
formData.append('file', chusankanFile);
const response = await api.post('/fields/import/chusankan/', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
const data = response.data;
setChusankanResult({
success: true,
message: data.message || 'インポートが完了しました',
created: data.created,
updated: data.updated,
});
} catch (error: unknown) {
console.error('Upload failed:', error);
let errorMessage = 'アップロードに失敗しました';
if (error && typeof error === 'object' && 'response' in error) {
const axiosError = error as { response?: { data?: { error?: string } } };
if (axiosError.response?.data?.error) {
errorMessage = axiosError.response.data.error;
}
}
setChusankanResult({
success: false,
message: errorMessage,
});
} finally {
setUploading(false);
}
};
return ( return (
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
<Navbar /> <Navbar />
@@ -195,6 +242,85 @@ export default function ImportPage() {
)} )}
</div> </div>
{/* 中山間マスタ取込 */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-2">
</h2>
<p className="text-sm text-gray-600 mb-4">
ID, , , ...
</p>
<div className="flex items-center gap-4 mb-4">
<input
ref={chusankanInputRef}
type="file"
accept=".ods"
onChange={(e) => setChusankanFile(e.target.files?.[0] || null)}
className="hidden"
/>
<button
onClick={() => chusankanInputRef.current?.click()}
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
>
{chusankanFile ? chusankanFile.name : 'ファイルを選択'}
</button>
{chusankanFile && (
<button
onClick={() => {
setChusankanFile(null);
setChusankanResult(null);
}}
className="text-gray-500 hover:text-gray-700"
>
</button>
)}
</div>
<button
onClick={handleChusankanUpload}
disabled={!chusankanFile || uploading}
className="w-full flex items-center justify-center px-4 py-2 bg-yellow-600 text-white rounded-md hover:bg-yellow-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{uploading ? (
<>
<Loader2 className="h-5 w-5 mr-2 animate-spin" />
...
</>
) : (
<>
<Upload className="h-5 w-5 mr-2" />
</>
)}
</button>
{chusankanResult && (
<div
className={`mt-4 p-3 rounded-md flex items-center ${
chusankanResult.success
? 'bg-green-50 text-green-700'
: 'bg-red-50 text-red-700'
}`}
>
{chusankanResult.success ? (
<CheckCircle className="h-5 w-5 mr-2" />
) : (
<XCircle className="h-5 w-5 mr-2" />
)}
<div>
<p className="font-medium">{chusankanResult.message}</p>
{chusankanResult.success && chusankanResult.created !== undefined && (
<p className="text-sm">
: {chusankanResult.created} / : {chusankanResult.updated}
</p>
)}
</div>
</div>
)}
</div>
{/* 実圃場データ取込 */} {/* 実圃場データ取込 */}
<div className="bg-white rounded-lg shadow p-6"> <div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-2"> <h2 className="text-lg font-semibold text-gray-900 mb-2">