Add rice transplant planning feature

This commit is contained in:
akira
2026-04-04 17:26:55 +09:00
parent f236fe2f90
commit 0c57dd7886
15 changed files with 1458 additions and 13 deletions

View File

@@ -367,6 +367,34 @@ export default function AllocationPage() {
}
};
const handleUpdateCropSeedInventory = async (cropId: number, seedInventoryKg: string) => {
try {
const crop = crops.find((item) => item.id === cropId);
if (!crop) return;
await api.patch(`/plans/crops/${cropId}/`, {
seed_inventory_kg: seedInventoryKg,
});
await fetchData(true);
} catch (error) {
console.error('Failed to update crop seed inventory:', error);
alert('種もみ在庫の更新に失敗しました');
}
};
const handleUpdateVarietyDefaultBoxes = async (varietyId: number, defaultBoxes: string) => {
try {
const variety = crops.flatMap((crop) => crop.varieties).find((item) => item.id === varietyId);
if (!variety) return;
await api.patch(`/plans/varieties/${varietyId}/`, {
default_seedling_boxes_per_tan: defaultBoxes,
});
await fetchData(true);
} catch (error) {
console.error('Failed to update variety default boxes:', error);
alert('品種デフォルトの更新に失敗しました');
}
};
const toggleFieldSelection = (fieldId: number) => {
setSelectedFields((prev) => {
const next = new Set(prev);
@@ -1029,18 +1057,34 @@ export default function AllocationPage() {
</div>
<div className="flex-1 overflow-y-auto p-4">
{managerCropId && (
<div className="mb-4 rounded-lg border border-amber-200 bg-amber-50 p-3">
<p className="mb-2 text-xs font-semibold text-amber-800"></p>
<CropSeedInventoryForm
crop={crops.find((crop) => crop.id === managerCropId) || null}
onSave={handleUpdateCropSeedInventory}
/>
</div>
)}
{managerCropId && getVarietiesForCrop(managerCropId).length > 0 ? (
<ul className="space-y-2">
{getVarietiesForCrop(managerCropId).map((v) => (
<li key={v.id} className="flex items-center justify-between p-2 rounded hover:bg-gray-50">
<span className="text-sm text-gray-900">{v.name}</span>
<button
onClick={() => handleDeleteVariety(v.id, v.name)}
className="text-red-400 hover:text-red-600 p-1"
title="削除"
>
<Trash2 className="h-4 w-4" />
</button>
<li key={v.id} className="rounded border border-gray-200 p-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-medium text-gray-900">{v.name}</span>
<button
onClick={() => handleDeleteVariety(v.id, v.name)}
className="text-red-400 hover:text-red-600 p-1"
title="削除"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
<VarietyDefaultBoxesForm
varietyId={v.id}
initialValue={v.default_seedling_boxes_per_tan}
onSave={handleUpdateVarietyDefaultBoxes}
/>
</li>
))}
</ul>
@@ -1105,3 +1149,90 @@ function VarietyAddForm({ cropId, onAdd }: { cropId: number | null; onAdd: (name
</div>
);
}
function CropSeedInventoryForm({
crop,
onSave,
}: {
crop: Crop | null;
onSave: (cropId: number, seedInventoryKg: string) => Promise<void>;
}) {
const [value, setValue] = useState(crop?.seed_inventory_kg ?? '0');
const [saving, setSaving] = useState(false);
useEffect(() => {
setValue(crop?.seed_inventory_kg ?? '0');
}, [crop?.id, crop?.seed_inventory_kg]);
const handleSave = async () => {
if (!crop) return;
setSaving(true);
await onSave(crop.id, value);
setSaving(false);
};
return (
<div className="flex items-end gap-2">
<div className="flex-1">
<label className="mb-1 block text-xs text-gray-600">(kg)</label>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
inputMode="decimal"
/>
</div>
<button
onClick={handleSave}
disabled={!crop || saving}
className="rounded-md bg-green-600 px-3 py-2 text-sm text-white hover:bg-green-700 disabled:opacity-50"
>
</button>
</div>
);
}
function VarietyDefaultBoxesForm({
varietyId,
initialValue,
onSave,
}: {
varietyId: number;
initialValue: string;
onSave: (varietyId: number, defaultBoxes: string) => Promise<void>;
}) {
const [value, setValue] = useState(initialValue);
const [saving, setSaving] = useState(false);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const handleSave = async () => {
setSaving(true);
await onSave(varietyId, value);
setSaving(false);
};
return (
<div className="flex items-end gap-2">
<div className="flex-1">
<label className="mb-1 block text-xs text-gray-600"></label>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
inputMode="decimal"
/>
</div>
<button
onClick={handleSave}
disabled={saving}
className="rounded-md bg-green-600 px-3 py-2 text-sm text-white hover:bg-green-700 disabled:opacity-50"
>
</button>
</div>
);
}