施肥散布実績機能を実装し運搬・作業記録・在庫連携を追加
This commit is contained in:
138
frontend/src/app/workrecords/page.tsx
Normal file
138
frontend/src/app/workrecords/page.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ChevronLeft, NotebookText } from 'lucide-react';
|
||||
|
||||
import Navbar from '@/components/Navbar';
|
||||
import { api } from '@/lib/api';
|
||||
import { WorkRecord } from '@/types';
|
||||
|
||||
const CURRENT_YEAR = new Date().getFullYear();
|
||||
const YEAR_KEY = 'workRecordYear';
|
||||
|
||||
export default function WorkRecordsPage() {
|
||||
const router = useRouter();
|
||||
const [year, setYear] = useState<number>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return parseInt(localStorage.getItem(YEAR_KEY) || String(CURRENT_YEAR), 10);
|
||||
}
|
||||
return CURRENT_YEAR;
|
||||
});
|
||||
const [records, setRecords] = useState<WorkRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(YEAR_KEY, String(year));
|
||||
void fetchRecords();
|
||||
}, [year]);
|
||||
|
||||
const fetchRecords = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await api.get(`/workrecords/?year=${year}`);
|
||||
setRecords(res.data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError('作業記録の読み込みに失敗しました。');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const moveToSource = (record: WorkRecord) => {
|
||||
if (record.spreading_session) {
|
||||
router.push(`/fertilizer/spreading?session=${record.spreading_session}`);
|
||||
return;
|
||||
}
|
||||
if (record.delivery_plan_id) {
|
||||
router.push(`/distribution/${record.delivery_plan_id}/edit`);
|
||||
}
|
||||
};
|
||||
|
||||
const years = Array.from({ length: 5 }, (_, i) => CURRENT_YEAR + 1 - i);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Navbar />
|
||||
<main className="mx-auto max-w-6xl px-4 py-8">
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<button onClick={() => router.push('/fertilizer')} className="text-gray-500 hover:text-gray-700">
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</button>
|
||||
<NotebookText className="h-6 w-6 text-green-700" />
|
||||
<h1 className="text-2xl font-bold text-gray-900">作業記録</h1>
|
||||
</div>
|
||||
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<label className="text-sm font-medium text-gray-700">年度:</label>
|
||||
<select
|
||||
value={year}
|
||||
onChange={(e) => setYear(Number(e.target.value))}
|
||||
className="rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
>
|
||||
{years.map((y) => (
|
||||
<option key={y} value={y}>
|
||||
{y}年度
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="overflow-hidden rounded-lg bg-white shadow-sm">
|
||||
{loading ? (
|
||||
<div className="px-5 py-8 text-sm text-gray-500">読み込み中...</div>
|
||||
) : records.length === 0 ? (
|
||||
<div className="px-5 py-8 text-sm text-gray-400">この年度の作業記録はまだありません。</div>
|
||||
) : (
|
||||
<table className="w-full text-sm">
|
||||
<thead className="border-b bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left font-medium text-gray-700">作業日</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-gray-700">種別</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-gray-700">タイトル</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-gray-700">参照先</th>
|
||||
<th className="px-4 py-3" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100">
|
||||
{records.map((record) => (
|
||||
<tr key={record.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-3 text-gray-700">{record.work_date}</td>
|
||||
<td className="px-4 py-3 text-gray-700">{record.work_type_display}</td>
|
||||
<td className="px-4 py-3 font-medium text-gray-900">{record.title}</td>
|
||||
<td className="px-4 py-3 text-gray-600">
|
||||
{record.spreading_session
|
||||
? `散布実績 #${record.spreading_session}`
|
||||
: record.delivery_plan_name
|
||||
? `${record.delivery_plan_name}`
|
||||
: '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
{(record.spreading_session || record.delivery_plan_id) && (
|
||||
<button
|
||||
onClick={() => moveToSource(record)}
|
||||
className="rounded border border-gray-300 px-2.5 py-1.5 text-xs text-gray-700 hover:bg-gray-100"
|
||||
>
|
||||
開く
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user