修正完了
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() {
|
||||
const [kyosaiFile, setKyosaiFile] = useState<File | null>(null);
|
||||
const [yoshidaFile, setYoshidaFile] = useState<File | null>(null);
|
||||
const [chusankanFile, setChusankanFile] = useState<File | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [kyosaiResult, setKyosaiResult] = useState<ImportResult | null>(null);
|
||||
const [yoshidaResult, setYoshidaResult] = useState<ImportResult | null>(null);
|
||||
const [chusankanResult, setChusankanResult] = useState<ImportResult | null>(null);
|
||||
const kyosaiInputRef = useRef<HTMLInputElement>(null);
|
||||
const yoshidaInputRef = useRef<HTMLInputElement>(null);
|
||||
const chusankanInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleKyosaiUpload = async () => {
|
||||
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 (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Navbar />
|
||||
@@ -195,6 +242,85 @@ export default function ImportPage() {
|
||||
)}
|
||||
</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">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
|
||||
Reference in New Issue
Block a user