From f236fe2f901400b9db62111c321e112c282adc5b Mon Sep 17 00:00:00 2001 From: akira Date: Sat, 4 Apr 2026 12:07:41 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=BD=E3=83=BC=E3=83=88=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=BE=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=80=82page.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 畔塗画面の対象圃場一覧で、圃場 / 面積 / グループ / 品種 の各ヘッダーを押すと昇順・降順を切り替えられます。初期状態は 圃場名昇順 です。選択状態はそのまま維持されるので、並べ替えてもチェックが外れることはありません。 必要なら次に、ソートだけでなく検索欄も足せます。圃場数が多いなら検索もかなり効きます。 --- frontend/src/app/levee-work/page.tsx | 95 +++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/levee-work/page.tsx b/frontend/src/app/levee-work/page.tsx index ce897dd..6117f6a 100644 --- a/frontend/src/app/levee-work/page.tsx +++ b/frontend/src/app/levee-work/page.tsx @@ -2,7 +2,7 @@ import { Suspense, useEffect, useMemo, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { ChevronLeft, PencilLine, Plus, Save, Trash2 } from 'lucide-react'; +import { ArrowDown, ArrowUp, ChevronLeft, PencilLine, Plus, Save, Trash2 } from 'lucide-react'; import Navbar from '@/components/Navbar'; import { api } from '@/lib/api'; @@ -18,6 +18,9 @@ type FormState = { selectedFieldIds: Set; }; +type SortKey = 'field_name' | 'field_area_tan' | 'group_name' | 'variety_name'; +type SortDirection = 'asc' | 'desc'; + const extractErrorMessage = (error: any) => { const data = error?.response?.data; if (!data) return '保存に失敗しました。'; @@ -64,6 +67,8 @@ function LeveeWorkPageContent() { const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [openedFromQuery, setOpenedFromQuery] = useState(false); + const [sortKey, setSortKey] = useState('field_name'); + const [sortDirection, setSortDirection] = useState('asc'); useEffect(() => { localStorage.setItem(YEAR_KEY, String(year)); @@ -173,10 +178,48 @@ function LeveeWorkPageContent() { const selectedCount = form?.selectedFieldIds.size ?? 0; + const sortedCandidates = useMemo(() => { + const rows = [...candidates]; + rows.sort((a, b) => { + let result = 0; + + if (sortKey === 'field_area_tan') { + result = Number(a.field_area_tan) - Number(b.field_area_tan); + } else { + result = (a[sortKey] || '').toString().localeCompare((b[sortKey] || '').toString(), 'ja'); + } + + if (result === 0) { + result = a.field_name.localeCompare(b.field_name, 'ja'); + } + + return sortDirection === 'asc' ? result : -result; + }); + return rows; + }, [candidates, sortDirection, sortKey]); + const selectedCandidates = useMemo(() => { if (!form) return []; - return candidates.filter((candidate) => form.selectedFieldIds.has(candidate.field_id)); - }, [candidates, form]); + return sortedCandidates.filter((candidate) => form.selectedFieldIds.has(candidate.field_id)); + }, [form, sortedCandidates]); + + const handleSort = (nextKey: SortKey) => { + if (sortKey === nextKey) { + setSortDirection((current) => (current === 'asc' ? 'desc' : 'asc')); + return; + } + setSortKey(nextKey); + setSortDirection('asc'); + }; + + const renderSortIcon = (key: SortKey) => { + if (sortKey !== key) return null; + return sortDirection === 'asc' ? ( + + ) : ( + + ); + }; const handleSave = async () => { if (!form) return; @@ -377,14 +420,50 @@ function LeveeWorkPageContent() { 選択 - 圃場 - 面積 - グループ - 品種 + + + + + + + + + + + + - {candidates.map((candidate) => { + {sortedCandidates.map((candidate) => { const checked = form.selectedFieldIds.has(candidate.field_id); return (