'use client'; import { Suspense, useEffect, useMemo, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { ChevronLeft, PencilLine, Plus, Save, Trash2 } from 'lucide-react'; import Navbar from '@/components/Navbar'; import { api } from '@/lib/api'; import { LeveeWorkCandidate, LeveeWorkSession } from '@/types'; const CURRENT_YEAR = new Date().getFullYear(); const YEAR_KEY = 'leveeWorkYear'; type FormState = { date: string; title: string; notes: string; selectedFieldIds: Set; }; const extractErrorMessage = (error: any) => { const data = error?.response?.data; if (!data) return '保存に失敗しました。'; if (typeof data.detail === 'string') return data.detail; if (Array.isArray(data.year) && data.year[0]) return data.year[0]; if (Array.isArray(data.items) && data.items[0]) return data.items[0]; if (typeof data.items === 'string') return data.items; return '保存に失敗しました。'; }; const getDefaultDate = (year: number) => { const today = new Date(); if (today.getFullYear() !== year) { return `${year}-01-01`; } const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; export default function LeveeWorkPage() { return (
読み込み中...
}>
); } function LeveeWorkPageContent() { const router = useRouter(); const searchParams = useSearchParams(); const [year, setYear] = useState(() => { if (typeof window !== 'undefined') { return parseInt(localStorage.getItem(YEAR_KEY) || String(CURRENT_YEAR), 10); } return CURRENT_YEAR; }); const [sessions, setSessions] = useState([]); const [candidates, setCandidates] = useState([]); const [form, setForm] = useState(null); const [editingSessionId, setEditingSessionId] = useState(null); const [loading, setLoading] = useState(true); const [formLoading, setFormLoading] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [openedFromQuery, setOpenedFromQuery] = useState(false); useEffect(() => { localStorage.setItem(YEAR_KEY, String(year)); void fetchSessions(); setForm(null); setEditingSessionId(null); setOpenedFromQuery(false); }, [year]); useEffect(() => { const sessionParam = Number(searchParams.get('session') || '0') || null; if (!sessionParam || openedFromQuery || sessions.length === 0) { return; } const target = sessions.find((session) => session.id === sessionParam); if (target) { void openEditor(target); setOpenedFromQuery(true); } }, [openedFromQuery, searchParams, sessions]); const fetchSessions = async () => { setLoading(true); setError(null); try { const res = await api.get(`/levee-work/sessions/?year=${year}`); setSessions(res.data); } catch (e) { console.error(e); setError('畔塗記録の読み込みに失敗しました。'); } finally { setLoading(false); } }; const loadCandidates = async () => { const res = await api.get(`/levee-work/candidates/?year=${year}`); setCandidates(res.data); return res.data as LeveeWorkCandidate[]; }; const startCreate = async () => { setFormLoading(true); setError(null); try { const loaded = await loadCandidates(); setEditingSessionId(null); setForm({ date: getDefaultDate(year), title: '水稲畔塗', notes: '', selectedFieldIds: new Set(loaded.filter((candidate) => candidate.selected).map((candidate) => candidate.field_id)), }); } catch (e) { console.error(e); setError('候補圃場の読み込みに失敗しました。'); } finally { setFormLoading(false); } }; const openEditor = async (session: LeveeWorkSession) => { setFormLoading(true); setError(null); try { const loaded = await loadCandidates(); const selectedIds = new Set(session.items.map((item) => item.field)); const fallbackSelected = loaded.filter((candidate) => candidate.selected).map((candidate) => candidate.field_id); setEditingSessionId(session.id); setForm({ date: session.date, title: session.title, notes: session.notes, selectedFieldIds: selectedIds.size > 0 ? selectedIds : new Set(fallbackSelected), }); } catch (e) { console.error(e); setError('編集用データの読み込みに失敗しました。'); } finally { setFormLoading(false); } }; const handleToggleField = (fieldId: number) => { if (!form) return; const next = new Set(form.selectedFieldIds); if (next.has(fieldId)) { next.delete(fieldId); } else { next.add(fieldId); } setForm({ ...form, selectedFieldIds: next }); }; const handleSelectAll = () => { if (!form) return; setForm({ ...form, selectedFieldIds: new Set(candidates.map((candidate) => candidate.field_id)), }); }; const handleClearAll = () => { if (!form) return; setForm({ ...form, selectedFieldIds: new Set() }); }; const selectedCount = form?.selectedFieldIds.size ?? 0; const selectedCandidates = useMemo(() => { if (!form) return []; return candidates.filter((candidate) => form.selectedFieldIds.has(candidate.field_id)); }, [candidates, form]); const handleSave = async () => { if (!form) return; if (selectedCount === 0) { setError('対象圃場を1件以上選択してください。'); return; } setSaving(true); setError(null); try { const payload = { year, date: form.date, title: form.title, notes: form.notes, items: selectedCandidates.map((candidate) => ({ field: candidate.field_id, plan: candidate.plan_id, })), }; if (editingSessionId) { await api.put(`/levee-work/sessions/${editingSessionId}/`, payload); } else { await api.post('/levee-work/sessions/', payload); } await fetchSessions(); await startCreate(); } catch (e: any) { console.error(e); setError(extractErrorMessage(e)); } finally { setSaving(false); } }; const handleDelete = async () => { if (!editingSessionId) return; if (!window.confirm('この畔塗記録を削除しますか?')) return; setSaving(true); setError(null); try { await api.delete(`/levee-work/sessions/${editingSessionId}/`); await fetchSessions(); setEditingSessionId(null); setForm(null); } catch (e) { console.error(e); setError('削除に失敗しました。'); } finally { setSaving(false); } }; const years = Array.from({ length: 5 }, (_, i) => CURRENT_YEAR + 1 - i); return (

畔塗記録

{error && (
{error}
)}
記録一覧
{loading ? (
読み込み中...
) : sessions.length === 0 ? (
この年度の畔塗記録はまだありません。
) : (
{sessions.map((session) => ( ))}
)}
{editingSessionId ? '畔塗記録を編集' : '畔塗記録を作成'}
{!form ? (
{formLoading ? 'フォームを準備中...' : '「新規作成」または既存記録の選択で編集を始められます。'}
) : (