施肥計画の計算設定を保存・復元し、未入力圃場のみ計算オプションを追加

- FertilizationPlanにcalc_settings JSONFieldを追加(migration 0004)
- 編集画面を開くと前回の計算方式・パラメータが復元される
- 「未入力圃場のみ」チェックで既存値を保持したまま新規圃場だけ計算可能

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Akira
2026-03-02 10:50:56 +09:00
parent 21d1dc355d
commit 5145217481
5 changed files with 58 additions and 11 deletions

View File

@@ -91,7 +91,13 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
const fertIds = Array.from(new Set(plan.entries.map((e) => e.fertilizer)));
const ferts = fertsRes.data.filter((f: Fertilizer) => fertIds.includes(f.id));
setPlanFertilizers(ferts);
setCalcSettings(ferts.map((f: Fertilizer) => ({ fertilizer_id: f.id, method: 'per_tan' as CalcMethod, param: '' })));
// 保存済み計算設定を復元、なければデフォルト値
setCalcSettings(ferts.map((f: Fertilizer) => {
const saved = plan.calc_settings?.find((s) => s.fertilizer_id === f.id);
return saved
? { fertilizer_id: f.id, method: saved.method as CalcMethod, param: saved.param }
: { fertilizer_id: f.id, method: 'per_tan' as CalcMethod, param: '' };
}));
const fieldIds = Array.from(new Set(plan.entries.map((e) => e.field)));
const fields = fieldsRes.data.filter((f: Field) => fieldIds.includes(f.id));
@@ -172,17 +178,29 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
setAdjusted((prev) => { const next = { ...prev }; delete next[id]; return next; });
};
const [calcNewOnly, setCalcNewOnly] = useState(true);
// ─── 自動計算
const runCalc = async (setting: CalcSetting) => {
if (!setting.param) return alert('パラメータを入力してください');
if (selectedFields.length === 0) return alert('対象圃場を選択してください');
const targetFields = calcNewOnly
? selectedFields.filter((f) => {
const hasAdjusted = adjusted[f.id]?.[setting.fertilizer_id] !== undefined;
const hasCalc = calcMatrix[f.id]?.[setting.fertilizer_id] !== undefined;
return !hasAdjusted && !hasCalc;
})
: selectedFields;
if (targetFields.length === 0) return alert('未入力の圃場がありません。「全圃場」で実行してください。');
try {
const res = await api.post('/fertilizer/calculate/', {
method: setting.method,
param: parseFloat(setting.param),
fertilizer_id: setting.fertilizer_id,
field_ids: selectedFields.map((f) => f.id),
field_ids: targetFields.map((f) => f.id),
});
const results: { field_id: number; bags: number }[] = res.data;
@@ -311,7 +329,7 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
setSaving(true);
try {
const payload = { name, year, variety: varietyId, entries };
const payload = { name, year, variety: varietyId, calc_settings: calcSettings, entries };
if (isNew) {
await api.post('/fertilizer/plans/', payload);
} else {
@@ -481,12 +499,23 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
<div className="bg-white rounded-lg shadow p-4 mb-4">
<div className="flex items-center justify-between mb-3">
<h2 className="text-sm font-semibold text-gray-700"></h2>
<button
onClick={() => setShowFertPicker(true)}
className="flex items-center gap-1 text-xs text-green-600 hover:text-green-800 border border-green-300 rounded px-2 py-1"
>
<Plus className="h-3 w-3" />
</button>
<div className="flex items-center gap-3">
<label className="flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer select-none">
<input
type="checkbox"
checked={calcNewOnly}
onChange={(e) => setCalcNewOnly(e.target.checked)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
</label>
<button
onClick={() => setShowFertPicker(true)}
className="flex items-center gap-1 text-xs text-green-600 hover:text-green-800 border border-green-300 rounded px-2 py-1"
>
<Plus className="h-3 w-3" />
</button>
</div>
</div>
{planFertilizers.length === 0 ? (
<p className="text-sm text-gray-400"></p>