'use client'; import { useEffect, useState } from 'react'; import { ChevronLeft, Pencil, Plus, Trash2 } from 'lucide-react'; import { useRouter } from 'next/navigation'; import MaterialForm, { MaterialFormState, MaterialTab, } from '../_components/MaterialForm'; import Navbar from '@/components/Navbar'; import { api } from '@/lib/api'; import { Material } from '@/types'; const tabs: { key: MaterialTab; label: string }[] = [ { key: 'fertilizer', label: '肥料' }, { key: 'pesticide', label: '農薬' }, { key: 'misc', label: 'その他' }, ]; const emptyForm = (tab: MaterialTab): MaterialFormState => ({ name: '', material_type: tab === 'fertilizer' ? 'fertilizer' : tab === 'pesticide' ? 'pesticide' : 'other', maker: '', stock_unit: tab === 'fertilizer' ? 'bag' : tab === 'pesticide' ? 'bottle' : 'piece', is_active: true, notes: '', fertilizer_profile: { capacity_kg: '', nitrogen_pct: '', phosphorus_pct: '', potassium_pct: '', }, pesticide_profile: { registration_no: '', formulation: '', usage_unit: '', dilution_ratio: '', active_ingredient: '', category: '', }, }); export default function MaterialMastersPage() { const router = useRouter(); const [tab, setTab] = useState('fertilizer'); const [materials, setMaterials] = useState([]); const [loading, setLoading] = useState(true); const [editingId, setEditingId] = useState(null); const [form, setForm] = useState(emptyForm('fertilizer')); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { fetchMaterials(); }, []); useEffect(() => { if (editingId === 'new') { setForm(emptyForm(tab)); } }, [tab, editingId]); const fetchMaterials = async () => { setLoading(true); try { const res = await api.get('/materials/materials/'); setMaterials(res.data); } catch (e) { console.error(e); setError('資材マスタの取得に失敗しました。'); } finally { setLoading(false); } }; const visibleMaterials = materials.filter((material) => { if (tab === 'misc') { return material.material_type === 'other' || material.material_type === 'seedling'; } return material.material_type === tab; }); const startNew = () => { setError(null); setForm(emptyForm(tab)); setEditingId('new'); }; const startEdit = (material: Material) => { setError(null); setForm({ name: material.name, material_type: material.material_type, maker: material.maker, stock_unit: material.stock_unit, is_active: material.is_active, notes: material.notes, fertilizer_profile: { capacity_kg: material.fertilizer_profile?.capacity_kg ?? '', nitrogen_pct: material.fertilizer_profile?.nitrogen_pct ?? '', phosphorus_pct: material.fertilizer_profile?.phosphorus_pct ?? '', potassium_pct: material.fertilizer_profile?.potassium_pct ?? '', }, pesticide_profile: { registration_no: material.pesticide_profile?.registration_no ?? '', formulation: material.pesticide_profile?.formulation ?? '', usage_unit: material.pesticide_profile?.usage_unit ?? '', dilution_ratio: material.pesticide_profile?.dilution_ratio ?? '', active_ingredient: material.pesticide_profile?.active_ingredient ?? '', category: material.pesticide_profile?.category ?? '', }, }); setEditingId(material.id); }; const cancelEdit = () => { setEditingId(null); setForm(emptyForm(tab)); }; const handleSave = async () => { setError(null); if (!form.name.trim()) { setError('資材名を入力してください。'); return; } setSaving(true); try { const payload = { name: form.name, material_type: form.material_type, maker: form.maker, stock_unit: form.stock_unit, is_active: form.is_active, notes: form.notes, fertilizer_profile: form.material_type === 'fertilizer' ? { capacity_kg: form.fertilizer_profile.capacity_kg || null, nitrogen_pct: form.fertilizer_profile.nitrogen_pct || null, phosphorus_pct: form.fertilizer_profile.phosphorus_pct || null, potassium_pct: form.fertilizer_profile.potassium_pct || null, } : undefined, pesticide_profile: form.material_type === 'pesticide' ? { registration_no: form.pesticide_profile.registration_no, formulation: form.pesticide_profile.formulation, usage_unit: form.pesticide_profile.usage_unit, dilution_ratio: form.pesticide_profile.dilution_ratio, active_ingredient: form.pesticide_profile.active_ingredient, category: form.pesticide_profile.category, } : undefined, }; if (editingId === 'new') { await api.post('/materials/materials/', payload); } else { await api.put(`/materials/materials/${editingId}/`, payload); } await fetchMaterials(); setEditingId(null); setForm(emptyForm(tab)); } catch (e: unknown) { console.error(e); const detail = typeof e === 'object' && e !== null && 'response' in e && typeof e.response === 'object' && e.response !== null && 'data' in e.response ? JSON.stringify(e.response.data) : '保存に失敗しました。'; setError(detail); } finally { setSaving(false); } }; const handleDelete = async (material: Material) => { setError(null); try { await api.delete(`/materials/materials/${material.id}/`); await fetchMaterials(); } catch (e: unknown) { console.error(e); const detail = typeof e === 'object' && e !== null && 'response' in e && typeof e.response === 'object' && e.response !== null && 'data' in e.response ? JSON.stringify(e.response.data) : `「${material.name}」の削除に失敗しました。`; setError(detail); } }; const handleBaseFieldChange = ( field: keyof Omit, value: string | boolean ) => { setForm((prev) => ({ ...prev, [field]: value, })); }; const handleFertilizerFieldChange = ( field: keyof MaterialFormState['fertilizer_profile'], value: string ) => { setForm((prev) => ({ ...prev, fertilizer_profile: { ...prev.fertilizer_profile, [field]: value, }, })); }; const handlePesticideFieldChange = ( field: keyof MaterialFormState['pesticide_profile'], value: string ) => { setForm((prev) => ({ ...prev, pesticide_profile: { ...prev.pesticide_profile, [field]: value, }, })); }; return (

資材マスタ管理

資材情報をインラインで編集できます。

{tabs.map((item) => ( ))}
{error && (
{error}
)} {loading ? (

読み込み中...

) : (
{tab === 'fertilizer' && ( )} {tab === 'pesticide' && ( )} {tab === 'misc' && ( )}
)}
); } interface TableProps { materials: Material[]; editingId: number | 'new' | null; form: MaterialFormState; saving: boolean; onEdit: (material: Material) => void; onDelete: (material: Material) => void; onBaseFieldChange: ( field: keyof Omit, value: string | boolean ) => void; onFertilizerFieldChange: ( field: keyof MaterialFormState['fertilizer_profile'], value: string ) => void; onPesticideFieldChange: ( field: keyof MaterialFormState['pesticide_profile'], value: string ) => void; onSave: () => void; onCancel: () => void; } function FertilizerTable(props: TableProps) { return ( {props.editingId === 'new' && } {props.materials.map((material) => props.editingId === material.id ? ( ) : ( ) )} {props.materials.length === 0 && props.editingId === null && ( )}
資材名 メーカー 1袋(kg) 窒素(%) リン酸(%) カリ(%) 単位 備考 使用中 操作
{material.name} {material.maker || '-'} {material.fertilizer_profile?.capacity_kg ?? '-'} {material.fertilizer_profile?.nitrogen_pct ?? '-'} {material.fertilizer_profile?.phosphorus_pct ?? '-'} {material.fertilizer_profile?.potassium_pct ?? '-'} {material.stock_unit_display} {material.notes || '-'} {material.is_active ? '○' : '-'} props.onEdit(material)} onDelete={() => props.onDelete(material)} />
該当する資材が登録されていません
); } function PesticideTable(props: TableProps) { return ( {props.editingId === 'new' && } {props.materials.map((material) => props.editingId === material.id ? ( ) : ( ) )} {props.materials.length === 0 && props.editingId === null && ( )}
資材名 メーカー 登録番号 剤型 有効成分 分類 単位 備考 使用中 操作
{material.name} {material.maker || '-'} {material.pesticide_profile?.registration_no || '-'} {material.pesticide_profile?.formulation || '-'} {material.pesticide_profile?.active_ingredient || '-'} {material.pesticide_profile?.category || '-'} {material.stock_unit_display} {material.notes || '-'} {material.is_active ? '○' : '-'} props.onEdit(material)} onDelete={() => props.onDelete(material)} />
該当する資材が登録されていません
); } function MiscTable(props: TableProps) { return ( {props.editingId === 'new' && } {props.materials.map((material) => props.editingId === material.id ? ( ) : ( ) )} {props.materials.length === 0 && props.editingId === null && ( )}
資材名 種別 メーカー 単位 備考 使用中 操作
{material.name} {material.material_type_display} {material.maker || '-'} {material.stock_unit_display} {material.notes || '-'} {material.is_active ? '○' : '-'} props.onEdit(material)} onDelete={() => props.onDelete(material)} />
該当する資材が登録されていません
); } function RowActions({ disabled, onEdit, onDelete, }: { disabled: boolean; onEdit: () => void; onDelete: () => void; }) { return (
); }