修正完了
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:
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user