From 592aedb665a80a48536681561d57161c63982f22 Mon Sep 17 00:00:00 2001 From: Akira Date: Sun, 15 Feb 2026 14:02:46 +0900 Subject: [PATCH] =?UTF-8?q?Day=2011=20=E5=AE=8C=E4=BA=86=20=E5=AE=9F?= =?UTF-8?q?=E8=A3=85=E5=86=85=E5=AE=B9:=201.=20frontend/src/components/Nav?= =?UTF-8?q?bar.tsx=20-=20=E3=83=87=E3=83=BC=E3=82=BF=E5=8F=96=E8=BE=BC?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E8=BF=BD=E5=8A=A0=202.=20frontend/s?= =?UTF-8?q?rc/app/import/page.tsx=20-=20=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=88=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=20=E6=A9=9F=E8=83=BD:=20-=20=E5=85=B1=E6=B8=88=E3=83=9E?= =?UTF-8?q?=E3=82=B9=E3=82=BF=E5=8F=96=E8=BE=BC=EF=BC=88POST=20/api/fields?= =?UTF-8?q?/import/kyosai/=EF=BC=89=20-=20=E5=AE=9F=E5=9C=83=E5=A0=B4?= =?UTF-8?q?=E3=83=87=E3=83=BC=E3=82=BF=E5=8F=96=E8=BE=BC=EF=BC=88POST=20/a?= =?UTF-8?q?pi/fields/import/yoshida/=EF=BC=89=20-=20=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E9=81=B8=E6=8A=9E=20(.ods)=20-=20=E7=B5=90?= =?UTF-8?q?=E6=9E=9C=E8=A1=A8=E7=A4=BA=EF=BC=88=E4=BD=9C=E6=88=90=E4=BB=B6?= =?UTF-8?q?=E6=95=B0=E3=80=81=E6=9B=B4=E6=96=B0=E4=BB=B6=E6=95=B0=EF=BC=89?= =?UTF-8?q?=20-=20=E3=82=A8=E3=83=A9=E3=83=BC=E8=A1=A8=E7=A4=BA=20API?= =?UTF-8?q?=E5=8B=95=E4=BD=9C=E7=A2=BA=E8=AA=8D:=20-=20/api/fields/import/?= =?UTF-8?q?kyosai/=20=E2=86=92=20HTTP=20400=EF=BC=88=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=AA=E3=81=97=E6=99=82=EF=BC=89=20-=20/a?= =?UTF-8?q?pi/fields/import/yoshida/=20=E2=86=92=20HTTP=20400=EF=BC=88?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AA=E3=81=97=E6=99=82?= =?UTF-8?q?=EF=BC=89=20=E3=83=96=E3=83=A9=E3=82=A6=E3=82=B6=E3=81=A7=20htt?= =?UTF-8?q?p://localhost:3000/import=20=E3=81=8B=E3=82=89=E3=83=87?= =?UTF-8?q?=E3=83=BC=E3=82=BF=E3=82=A4=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=81=8C=E5=8F=AF=E8=83=BD=E3=81=A7=E3=81=99=E3=80=82=20?= =?UTF-8?q?=E6=AC=A1=E3=81=AE=E5=B7=A5=E7=A8=8B=E3=81=AB=E7=A7=BB=E3=82=8A?= =?UTF-8?q?=E3=81=BE=E3=81=99=E3=81=8B=EF=BC=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/import/page.tsx | 280 +++++++++++++++++++++++++++++ frontend/src/components/Navbar.tsx | 13 +- 2 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/import/page.tsx diff --git a/frontend/src/app/import/page.tsx b/frontend/src/app/import/page.tsx new file mode 100644 index 0000000..552f50b --- /dev/null +++ b/frontend/src/app/import/page.tsx @@ -0,0 +1,280 @@ +'use client'; + +import { useState, useRef } from 'react'; +import { api } from '@/lib/api'; +import Navbar from '@/components/Navbar'; +import { Upload, Loader2, CheckCircle, XCircle } from 'lucide-react'; + +interface ImportResult { + success: boolean; + message: string; + created?: number; + updated?: number; +} + +export default function ImportPage() { + const [kyosaiFile, setKyosaiFile] = useState(null); + const [yoshidaFile, setYoshidaFile] = useState(null); + const [uploading, setUploading] = useState(false); + const [kyosaiResult, setKyosaiResult] = useState(null); + const [yoshidaResult, setYoshidaResult] = useState(null); + const kyosaiInputRef = useRef(null); + const yoshidaInputRef = useRef(null); + + const handleKyosaiUpload = async () => { + if (!kyosaiFile) { + alert('ファイルを選択してください'); + return; + } + + setUploading(true); + setKyosaiResult(null); + + try { + const formData = new FormData(); + formData.append('file', kyosaiFile); + + const response = await api.post('/fields/import/kyosai/', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + const data = response.data; + setKyosaiResult({ + 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; + } + } + setKyosaiResult({ + success: false, + message: errorMessage, + }); + } finally { + setUploading(false); + } + }; + + const handleYoshidaUpload = async () => { + if (!yoshidaFile) { + alert('ファイルを選択してください'); + return; + } + + setUploading(true); + setYoshidaResult(null); + + try { + const formData = new FormData(); + formData.append('file', yoshidaFile); + + const response = await api.post('/fields/import/yoshida/', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + const data = response.data; + setYoshidaResult({ + 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; + } + } + setYoshidaResult({ + success: false, + message: errorMessage, + }); + } finally { + setUploading(false); + } + }; + + return ( +
+ +
+

データインポート

+ +
+ {/* 共済マスタ取込 */} +
+

+ 共済マスタ取込 +

+

+ 共済細目データをインポートします(k_num, s_num, address...) +

+ +
+ setKyosaiFile(e.target.files?.[0] || null)} + className="hidden" + /> + + {kyosaiFile && ( + + )} +
+ + + + {kyosaiResult && ( +
+ {kyosaiResult.success ? ( + + ) : ( + + )} +
+

{kyosaiResult.message}

+ {kyosaiResult.success && kyosaiResult.created !== undefined && ( +

+ 作成: {kyosaiResult.created}件 / 更新: {kyosaiResult.updated}件 +

+ )} +
+
+ )} +
+ + {/* 実圃場データ取込 */} +
+

+ 実圃場データ取込 +

+

+ 吉田農地台帳データをインポートします +

+ +
+ setYoshidaFile(e.target.files?.[0] || null)} + className="hidden" + /> + + {yoshidaFile && ( + + )} +
+ + + + {yoshidaResult && ( +
+ {yoshidaResult.success ? ( + + ) : ( + + )} +
+

{yoshidaResult.message}

+ {yoshidaResult.success && yoshidaResult.created !== undefined && ( +

+ 作成: {yoshidaResult.created}件 / 更新: {yoshidaResult.updated}件 +

+ )} +
+
+ )} +
+
+
+
+ ); +} diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index ae6ba99..d6da2d2 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,7 +1,7 @@ 'use client'; import { useRouter, usePathname } from 'next/navigation'; -import { LogOut, Wheat, MapPin, FileText } from 'lucide-react'; +import { LogOut, Wheat, MapPin, FileText, Upload } from 'lucide-react'; import { logout } from '@/lib/api'; export default function Navbar() { @@ -54,6 +54,17 @@ export default function Navbar() { 帳票出力 +