見直し前の最終

This commit is contained in:
Akira
2026-02-16 13:45:16 +09:00
parent 4486722949
commit 9c21caa017
8 changed files with 953 additions and 160 deletions

View File

@@ -39,7 +39,7 @@ export default function AllocationPage() {
if (!background) setLoading(true);
try {
const [fieldsRes, cropsRes, plansRes] = await Promise.all([
api.get('/fields/'),
api.get('/fields/?ordering=group_name,display_order,id'),
api.get('/plans/crops/'),
api.get(`/plans/?year=${year}`),
]);
@@ -215,7 +215,6 @@ export default function AllocationPage() {
};
const handleGroupChange = async (fieldId: number, groupName: string) => {
// ローカル状態を先に更新(並び替え防止)
setFields(prev => prev.map(f =>
f.id === fieldId ? { ...f, group_name: groupName || null } : f
));
@@ -226,7 +225,6 @@ export default function AllocationPage() {
});
} catch (error) {
console.error('Failed to save group:', error);
// エラー時は再取得
await fetchData(true);
}
};
@@ -448,155 +446,155 @@ export default function AllocationPage() {
</p>
</div>
) : (
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
<p className="text-sm text-blue-800">
💡
</p>
</div>
)}
<>
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
<p className="text-sm text-blue-800">
💡
</p>
</div>
{sortedFields.length > 0 && (
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
{sortType === 'custom' && (
<th className="px-2 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-20">
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
{sortType === 'custom' && (
<th className="px-2 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-20">
</th>
)}
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
)}
<th className="px-4 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>
<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>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{sortedFields.map((field, index) => {
const plan = getPlanForField(field.id);
const selectedCropId = plan?.crop || 0;
const selectedVarietyId = plan?.variety || 0;
<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>
<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>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{sortedFields.map((field, index) => {
const plan = getPlanForField(field.id);
const selectedCropId = plan?.crop || 0;
const selectedVarietyId = plan?.variety || 0;
return (
<tr key={field.id} className="hover:bg-gray-50">
{sortType === 'custom' && (
<td className="px-2 py-4 whitespace-nowrap">
<div className="flex items-center gap-1">
<button
onClick={() => moveUp(index)}
disabled={index === 0 || saving === field.id}
className="p-1 hover:bg-gray-100 rounded disabled:opacity-30"
title="上へ移動"
>
<ArrowUp className="h-4 w-4" />
</button>
<button
onClick={() => moveDown(index)}
disabled={index === sortedFields.length - 1 || saving === field.id}
className="p-1 hover:bg-gray-100 rounded disabled:opacity-30"
title="下へ移動"
>
<ArrowDown className="h-4 w-4" />
</button>
return (
<tr key={field.id} className="hover:bg-gray-50">
{sortType === 'custom' && (
<td className="px-2 py-4 whitespace-nowrap">
<div className="flex items-center gap-1">
<button
onClick={() => moveUp(index)}
disabled={index === 0 || saving === field.id}
className="p-1 hover:bg-gray-100 rounded disabled:opacity-30"
title="上へ移動"
>
<ArrowUp className="h-4 w-4" />
</button>
<button
onClick={() => moveDown(index)}
disabled={index === sortedFields.length - 1 || saving === field.id}
className="p-1 hover:bg-gray-100 rounded disabled:opacity-30"
title="下へ移動"
>
<ArrowDown className="h-4 w-4" />
</button>
</div>
</td>
)}
<td className="px-4 py-4 whitespace-nowrap">
<input
list="group-options"
value={field.group_name || ''}
onChange={(e) => handleGroupChange(field.id, e.target.value)}
disabled={saving === field.id}
placeholder="選択または入力"
className="w-36 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500 disabled:opacity-50"
/>
<datalist id="group-options">
{groupOptions.map((g) => (
<option key={g} value={g} />
))}
</datalist>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">
{field.name}
</div>
<div className="text-xs text-gray-500">
{field.address}
</div>
</td>
)}
<td className="px-4 py-4 whitespace-nowrap">
<input
list="group-options"
value={field.group_name || ''}
onChange={(e) => handleGroupChange(field.id, e.target.value)}
disabled={saving === field.id}
placeholder="選択または入力"
className="w-36 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500 disabled:opacity-50"
/>
<datalist id="group-options">
{groupOptions.map((g) => (
<option key={g} value={g} />
))}
</datalist>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">
{field.name}
</div>
<div className="text-xs text-gray-500">
{field.address}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{field.area_tan}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<select
value={selectedCropId || ''}
onChange={(e) =>
handleCropChange(field.id, e.target.value)
}
disabled={saving === field.id}
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 text-sm disabled:opacity-50"
>
<option value=""></option>
{crops.map((crop) => (
<option key={crop.id} value={crop.id}>
{crop.name}
</option>
))}
</select>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<select
value={selectedVarietyId || ''}
onChange={(e) =>
handleVarietyChange(field.id, e.target.value)
}
disabled={
saving === field.id || !selectedCropId
}
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 text-sm disabled:opacity-50 disabled:bg-gray-100"
>
<option value=""></option>
{getVarietiesForCrop(selectedCropId).map((variety) => (
<option key={variety.id} value={variety.id}>
{variety.name}
</option>
))}
</select>
</td>
<td className="px-6 py-4">
<input
type="text"
value={plan?.notes || ''}
onChange={(e) =>
handleNotesChange(field.id, e.target.value)
}
disabled={saving === field.id || !plan}
placeholder="備考を入力"
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 text-sm w-full disabled:opacity-50 disabled:bg-gray-100"
/>
</td>
</tr>
);
})}
</tbody>
</table>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{field.area_tan}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<select
value={selectedCropId || ''}
onChange={(e) =>
handleCropChange(field.id, e.target.value)
}
disabled={saving === field.id}
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 text-sm disabled:opacity-50"
>
<option value=""></option>
{crops.map((crop) => (
<option key={crop.id} value={crop.id}>
{crop.name}
</option>
))}
</select>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<select
value={selectedVarietyId || ''}
onChange={(e) =>
handleVarietyChange(field.id, e.target.value)
}
disabled={
saving === field.id || !selectedCropId
}
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 text-sm disabled:opacity-50 disabled:bg-gray-100"
>
<option value=""></option>
{getVarietiesForCrop(selectedCropId).map((variety) => (
<option key={variety.id} value={variety.id}>
{variety.name}
</option>
))}
</select>
</td>
<td className="px-6 py-4">
<input
type="text"
value={plan?.notes || ''}
onChange={(e) =>
handleNotesChange(field.id, e.target.value)
}
disabled={saving === field.id || !plan}
placeholder="備考を入力"
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 text-sm w-full disabled:opacity-50 disabled:bg-gray-100"
/>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
</>
)}
</div>
</div>