見直し前の最終
This commit is contained in:
@@ -5,23 +5,27 @@ import { useRouter } from 'next/navigation';
|
||||
import { api } from '@/lib/api';
|
||||
import { Field } from '@/types';
|
||||
import Navbar from '@/components/Navbar';
|
||||
import { Plus, Pencil, Trash2 } from 'lucide-react';
|
||||
import { Plus, Pencil, Trash2, ArrowUp, ArrowDown } from 'lucide-react';
|
||||
|
||||
export default function FieldsPage() {
|
||||
const router = useRouter();
|
||||
const [fields, setFields] = useState<Field[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deleting, setDeleting] = useState<number | null>(null);
|
||||
const [uniqueGroups, setUniqueGroups] = useState<string[]>([]);
|
||||
const [sortOrder, setSortOrder] = useState('group_name,display_order,id');
|
||||
|
||||
useEffect(() => {
|
||||
fetchFields();
|
||||
}, []);
|
||||
}, [sortOrder]);
|
||||
|
||||
const fetchFields = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await api.get('/fields/');
|
||||
const response = await api.get(`/fields/?ordering=${sortOrder}`);
|
||||
setFields(response.data);
|
||||
const groups = [...new Set(response.data.map((f: Field) => f.group_name).filter(Boolean))] as string[];
|
||||
setUniqueGroups(groups.sort());
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch fields:', error);
|
||||
} finally {
|
||||
@@ -46,6 +50,52 @@ export default function FieldsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
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];
|
||||
|
||||
const currentOrder = currentField.display_order ?? 0;
|
||||
const targetOrder = targetField.display_order ?? 0;
|
||||
|
||||
try {
|
||||
await api.patch(`/fields/${currentField.id}/`, { display_order: targetOrder });
|
||||
await api.patch(`/fields/${targetField.id}/`, { display_order: currentOrder });
|
||||
await fetchFields();
|
||||
} catch (error) {
|
||||
console.error('Failed to reorder:', error);
|
||||
alert('順序の変更に失敗しました');
|
||||
}
|
||||
};
|
||||
|
||||
const isDisplayOrderMode = sortOrder === 'display_order,group_name,id';
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
@@ -63,13 +113,27 @@ export default function FieldsPage() {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold text-gray-900">圃場管理</h1>
|
||||
<button
|
||||
onClick={() => router.push('/fields/new')}
|
||||
className="flex items-center px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
新規作成
|
||||
</button>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm text-gray-600">並び順:</label>
|
||||
<select
|
||||
value={sortOrder}
|
||||
onChange={(e) => setSortOrder(e.target.value)}
|
||||
className="border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-green-500"
|
||||
>
|
||||
<option value="group_name,display_order,id">グループ優先</option>
|
||||
<option value="display_order,group_name,id">表示順優先</option>
|
||||
<option value="id">登録順</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => router.push('/fields/new')}
|
||||
className="flex items-center px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
新規作成
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{fields.length === 0 ? (
|
||||
@@ -78,13 +142,24 @@ export default function FieldsPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg shadow overflow-hidden">
|
||||
<datalist id="groups">
|
||||
{uniqueGroups.map((group) => (
|
||||
<option key={group} value={group} />
|
||||
))}
|
||||
</datalist>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-2 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider w-16">
|
||||
順序
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
圃場名
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
グループ
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
住所
|
||||
</th>
|
||||
@@ -103,13 +178,42 @@ export default function FieldsPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{fields.map((field) => (
|
||||
{fields.map((field, index) => (
|
||||
<tr key={field.id} className="hover:bg-gray-50">
|
||||
<td className="px-2 py-4 whitespace-nowrap text-center">
|
||||
<div className="flex items-center justify-center space-x-1">
|
||||
<button
|
||||
onClick={() => handleMoveOrder(index, 'up')}
|
||||
disabled={!isDisplayOrderMode || index === 0}
|
||||
className="p-1 text-gray-400 hover:text-gray-600 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
title={!isDisplayOrderMode ? "表示順優先モードで操作してください" : "上へ移動"}
|
||||
>
|
||||
<ArrowUp className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleMoveOrder(index, 'down')}
|
||||
disabled={!isDisplayOrderMode || index === fields.length - 1}
|
||||
className="p-1 text-gray-400 hover:text-gray-600 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
title={!isDisplayOrderMode ? "表示順優先モードで操作してください" : "下へ移動"}
|
||||
>
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{field.name}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<input
|
||||
list="groups"
|
||||
defaultValue={field.group_name || ''}
|
||||
onBlur={(e) => 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"
|
||||
/>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-sm text-gray-500">
|
||||
{field.address || '-'}
|
||||
|
||||
Reference in New Issue
Block a user