'use client'; import { useState, useEffect, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { api } from '@/lib/api'; import { Field, OfficialKyosaiField, OfficialChusankanField } from '@/types'; import Navbar from '@/components/Navbar'; import LinkModal from '@/components/LinkModal'; import { Plus, Pencil, Trash2, ArrowUp, ArrowDown, X } from 'lucide-react'; type ViewMode = 'normal' | 'mapping'; export default function FieldsPage() { const router = useRouter(); const [fields, setFields] = useState([]); const [loading, setLoading] = useState(true); const [deleting, setDeleting] = useState(null); const [uniqueGroups, setUniqueGroups] = useState([]); const [sortOrder, setSortOrder] = useState('group_name,display_order,id'); const [viewMode, setViewMode] = useState('normal'); // Modal state for mapping mode const [modalFieldId, setModalFieldId] = useState(null); const [modalType, setModalType] = useState<'kyosai' | 'chusankan' | null>(null); const [allKyosai, setAllKyosai] = useState([]); const [allChusankan, setAllChusankan] = useState([]); useEffect(() => { fetchFields(); }, [sortOrder]); const fetchFields = async () => { setLoading(true); try { const response = await api.get(`/fields/?ordering=${sortOrder}`); setFields(response.data); const groups = Array.from(new Set(response.data.map((f: Field) => f.group_name).filter(Boolean))); setUniqueGroups(groups.sort()); } catch (error) { console.error('Failed to fetch fields:', error); } finally { setLoading(false); } }; const handleDelete = async (id: number) => { if (!confirm('この圃場を削除してもよろしいですか?')) return; setDeleting(id); try { await api.delete(`/fields/${id}/`); await fetchFields(); } catch (error) { console.error('Failed to delete field:', error); alert('削除に失敗しました'); } finally { setDeleting(null); } }; const handleGroupChange = async (fieldId: number, newGroup: string) => { try { await api.patch(`/fields/${fieldId}/`, { group_name: newGroup || null }); if (newGroup && !uniqueGroups.includes(newGroup)) { setUniqueGroups([...uniqueGroups, newGroup].sort()); } if (sortOrder !== 'id') await fetchFields(); } catch (error) { console.error('Failed to update group:', error); alert('グループの更新に失敗しました'); } }; const handleMoveOrder = async (index: number, direction: 'up' | 'down') => { const newIndex = direction === 'up' ? index - 1 : index + 1; if (newIndex < 0 || newIndex >= fields.length) return; if (sortOrder !== 'display_order,group_name,id') { setSortOrder('display_order,group_name,id'); return; } const currentField = fields[index]; const targetField = fields[newIndex]; try { await api.patch(`/fields/${currentField.id}/`, { display_order: targetField.display_order ?? 0 }); await api.patch(`/fields/${targetField.id}/`, { display_order: currentField.display_order ?? 0 }); await fetchFields(); } catch (error) { console.error('Failed to reorder:', error); alert('順序の変更に失敗しました'); } }; // --- Mapping mode link management --- const openLinkModal = async (fieldId: number, type: 'kyosai' | 'chusankan') => { try { if (type === 'kyosai') { const res = await api.get('/kyosai-fields/'); setAllKyosai(res.data); } else { const res = await api.get('/chusankan-fields/'); setAllChusankan(res.data); } setModalFieldId(fieldId); setModalType(type); } catch (err) { console.error('Failed to fetch master data:', err); } }; const addLinks = async (ids: number[]) => { if (!modalFieldId || !modalType) return; try { if (modalType === 'kyosai') { await api.post(`/fields/${modalFieldId}/kyosai-links/`, { kyosai_field_ids: ids }); } else { await api.post(`/fields/${modalFieldId}/chusankan-links/`, { chusankan_field_ids: ids }); } await fetchFields(); } catch (err) { console.error('Failed to add links:', err); } }; const removeKyosaiLink = async (fieldId: number, kyosaiId: number) => { if (!confirm('この共済区画の紐づけを解除しますか?')) return; try { await api.delete(`/fields/${fieldId}/kyosai-links/${kyosaiId}/`); await fetchFields(); } catch (err) { console.error('Failed to remove kyosai link:', err); } }; const removeChusankanLink = async (fieldId: number, chusankanId: number) => { if (!confirm('この中山間区画の紐づけを解除しますか?')) return; try { await api.delete(`/fields/${fieldId}/chusankan-links/${chusankanId}/`); await fetchFields(); } catch (err) { console.error('Failed to remove chusankan link:', err); } }; const currentFieldForModal = modalFieldId ? fields.find((f) => f.id === modalFieldId) : null; const kyosaiLinkedIds = useMemo(() => { if (!currentFieldForModal) return new Set(); return new Set(currentFieldForModal.kyosai_fields.map((k) => k.id)); }, [currentFieldForModal]); const chusankanLinkedIds = useMemo(() => { if (!currentFieldForModal) return new Set(); return new Set(currentFieldForModal.chusankan_fields.map((c) => c.id)); }, [currentFieldForModal]); const isDisplayOrderMode = sortOrder === 'display_order,group_name,id'; if (loading) { return (
読み込み中...
); } return (

圃場管理

{/* View mode toggle */}
{fields.length === 0 ? (

圃場データがありません。「新規作成」ボタンから追加してください。

) : viewMode === 'mapping' ? ( /* ===== 対応表モード ===== */
{fields.map((field) => ( {/* 共済セル */} {/* 中山間セル */} ))}
圃場名 住所 面積(反) 共済(漢字地名) 中山間(所在地)
{field.address || '-'} {field.area_tan || '-'} {field.kyosai_fields.length > 0 ? (
{field.kyosai_fields.map((k) => (
{k.k_num}{k.s_num ? `-${k.s_num}` : ''} {' '}{k.kanji_name}
))}
) : ( - )}
{field.chusankan_fields.length > 0 ? (
{field.chusankan_fields.map((c) => (
ID{c.c_id} {' '}{c.oaza} {c.aza} {c.chiban}
))}
) : ( - )}
) : ( /* ===== 通常モード ===== */
{uniqueGroups.map((group) => (
{fields.map((field, index) => ( ))}
順序 圃場名 グループ 住所 面積(反) 面積(m2) 所有者 共済 中山間 操作
{field.name}
handleGroupChange(field.id, e.target.value)} placeholder="グループ名" className="w-32 text-sm border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-green-500" />
{field.address || '-'}
{field.area_tan || '-'} {field.area_m2 || '-'} {field.owner_name || '-'} {field.kyosai_fields?.length > 0 ? `${field.kyosai_fields.length}件` : '-'} {field.chusankan_fields?.length > 0 ? `${field.chusankan_fields.length}件` : '-'}
)}
{/* Link Modal */} {modalType === 'kyosai' && modalFieldId && ( (
{k.k_num}{k.s_num ? `-${k.s_num}` : ''} {' '}{k.kanji_name} {k.area.toLocaleString()}m2 {k.linked_field_names && k.linked_field_names.length > 0 && ( ({k.linked_field_names.join(', ')}) )}
)} searchFilter={(k: OfficialKyosaiField, q: string) => k.kanji_name.toLowerCase().includes(q) || k.address.toLowerCase().includes(q) || k.k_num.includes(q) } onAdd={addLinks} onClose={() => { setModalFieldId(null); setModalType(null); }} /> )} {modalType === 'chusankan' && modalFieldId && ( (
ID{c.c_id} {' '}{c.oaza} {c.aza} {c.chiban} {c.area.toLocaleString()}m2 {c.linked_field_names && c.linked_field_names.length > 0 && ( ({c.linked_field_names.join(', ')}) )}
)} searchFilter={(c: OfficialChusankanField, q: string) => c.c_id.includes(q) || c.oaza.toLowerCase().includes(q) || c.aza.toLowerCase().includes(q) || c.chiban.includes(q) } onAdd={addLinks} onClose={() => { setModalFieldId(null); setModalType(null); }} /> )}
); }