Add allocation variety change history UI
This commit is contained in:
@@ -4,7 +4,7 @@ import { useState, useEffect, useMemo } from 'react';
|
||||
import { api } from '@/lib/api';
|
||||
import { Field, Crop, Plan } from '@/types';
|
||||
import Navbar from '@/components/Navbar';
|
||||
import { Menu, X, BarChart3, ChevronRight, ChevronDown, ArrowUp, ArrowDown, Copy, Settings, Trash2, CheckSquare, Search } from 'lucide-react';
|
||||
import { Menu, X, BarChart3, ChevronRight, ChevronDown, ArrowUp, ArrowDown, Copy, Settings, Trash2, CheckSquare, Search, History } from 'lucide-react';
|
||||
|
||||
interface SummaryItem {
|
||||
cropId: number;
|
||||
@@ -48,6 +48,13 @@ export default function AllocationPage() {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [filterCropId, setFilterCropId] = useState<number | 0>(0);
|
||||
const [filterUnassigned, setFilterUnassigned] = useState(false);
|
||||
const [toast, setToast] = useState<{ type: 'success' | 'error'; message: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!toast) return;
|
||||
const timer = window.setTimeout(() => setToast(null), 4000);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [toast]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('allocationYear', String(year));
|
||||
@@ -233,17 +240,46 @@ export default function AllocationPage() {
|
||||
const existingPlan = getPlanForField(fieldId);
|
||||
|
||||
if (!existingPlan || !existingPlan.crop) return;
|
||||
if ((existingPlan.variety || null) === variety) return;
|
||||
|
||||
const nextVarietyName =
|
||||
variety === null
|
||||
? '(品種未選択)'
|
||||
: getVarietiesForCrop(existingPlan.crop).find((item) => item.id === variety)?.name || '不明';
|
||||
const currentVarietyName = existingPlan.variety_name || '(品種未選択)';
|
||||
|
||||
const shouldProceed = confirm(
|
||||
[
|
||||
`品種を「${currentVarietyName}」から「${nextVarietyName}」へ変更します。`,
|
||||
'施肥計画・田植え計画の関連エントリが自動で移動する場合があります。',
|
||||
'実行しますか?',
|
||||
].join('\n')
|
||||
);
|
||||
if (!shouldProceed) return;
|
||||
|
||||
setSaving(fieldId);
|
||||
|
||||
try {
|
||||
await api.patch(`/plans/${existingPlan.id}/`, {
|
||||
const res = await api.patch(`/plans/${existingPlan.id}/`, {
|
||||
variety,
|
||||
notes: existingPlan.notes,
|
||||
});
|
||||
const updatedPlan: Plan = res.data;
|
||||
const movedCount = updatedPlan.latest_variety_change?.fertilizer_moved_entry_count ?? 0;
|
||||
setToast({
|
||||
type: 'success',
|
||||
message:
|
||||
movedCount > 0
|
||||
? `品種を変更し、施肥計画 ${movedCount} 件を移動しました。`
|
||||
: '品種を変更しました。関連する施肥計画の移動はありませんでした。',
|
||||
});
|
||||
await fetchData(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to save variety:', error);
|
||||
setToast({
|
||||
type: 'error',
|
||||
message: '品種変更に失敗しました。',
|
||||
});
|
||||
} finally {
|
||||
setSaving(null);
|
||||
}
|
||||
@@ -563,6 +599,17 @@ export default function AllocationPage() {
|
||||
{/* メインコンテンツ */}
|
||||
<div className="flex-1 min-w-0 p-4 lg:p-0">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{toast && (
|
||||
<div
|
||||
className={`mb-4 rounded-md border px-4 py-3 text-sm ${
|
||||
toast.type === 'success'
|
||||
? 'border-green-300 bg-green-50 text-green-800'
|
||||
: 'border-red-300 bg-red-50 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{toast.message}
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
作付け計画 <span className="text-green-700">{year}年度</span>
|
||||
@@ -887,27 +934,43 @@ export default function AllocationPage() {
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<select
|
||||
value={selectedVarietyId || ''}
|
||||
onChange={(e) => {
|
||||
if (e.target.value === '__add__') {
|
||||
setAddingVariety({ fieldId: field.id, cropId: selectedCropId });
|
||||
setNewVarietyName('');
|
||||
} else {
|
||||
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>
|
||||
))}
|
||||
{selectedCropId > 0 && <option value="__add__">+ 新しい品種を追加...</option>}
|
||||
</select>
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={selectedVarietyId || ''}
|
||||
onChange={(e) => {
|
||||
if (e.target.value === '__add__') {
|
||||
setAddingVariety({ fieldId: field.id, cropId: selectedCropId });
|
||||
setNewVarietyName('');
|
||||
} else {
|
||||
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>
|
||||
))}
|
||||
{selectedCropId > 0 && <option value="__add__">+ 新しい品種を追加...</option>}
|
||||
</select>
|
||||
{plan?.latest_variety_change && (
|
||||
<div
|
||||
className="inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-50 px-2 py-1 text-xs text-amber-800"
|
||||
title={[
|
||||
`変更日時: ${new Date(plan.latest_variety_change.changed_at).toLocaleString('ja-JP')}`,
|
||||
`変更前: ${plan.latest_variety_change.old_variety_name || '未設定'}`,
|
||||
`変更後: ${plan.latest_variety_change.new_variety_name || '未設定'}`,
|
||||
`施肥移動件数: ${plan.latest_variety_change.fertilizer_moved_entry_count}`,
|
||||
].join('\n')}
|
||||
>
|
||||
<History className="h-3 w-3" />
|
||||
変更履歴あり
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
|
||||
Reference in New Issue
Block a user