359 lines
15 KiB
TypeScript
359 lines
15 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { useAppContext } from '../../context/AppContext.tsx';
|
|
import { apiClient } from '../../services/apiService.ts';
|
|
import { Kandang } from '../../types/counting.ts';
|
|
|
|
const KandangManagementPage: React.FC = () => {
|
|
const { cycleData } = useAppContext();
|
|
const [kandangs, setKandangs] = useState<Kandang[]>([]);
|
|
const [newName, setNewName] = useState('');
|
|
const [editingId, setEditingId] = useState<number | null>(null);
|
|
const [editName, setEditName] = useState('');
|
|
const [editDocInCount, setEditDocInCount] = useState('');
|
|
const [isAdding, setIsAdding] = useState(false);
|
|
const [newDocInCount, setNewDocInCount] = useState('');
|
|
|
|
const loadKandangs = async () => {
|
|
try {
|
|
const data = await apiClient.getKandangsByCycle(cycleData.id);
|
|
setKandangs(data);
|
|
} catch (error) {
|
|
console.error('Failed to load kandangs:', error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadKandangs();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [cycleData]);
|
|
|
|
const handleAdd = async () => {
|
|
if (!newName.trim()) return;
|
|
try {
|
|
const kandang = await apiClient.createKandang(newName.trim());
|
|
const docCount = parseInt(newDocInCount, 10);
|
|
if (!isNaN(docCount) && docCount > 0) {
|
|
await apiClient.setKandangDocInCount(kandang.id, cycleData.id, docCount);
|
|
}
|
|
setNewName('');
|
|
setNewDocInCount('');
|
|
setIsAdding(false);
|
|
loadKandangs();
|
|
} catch (error) {
|
|
console.error('Failed to create kandang:', error);
|
|
alert('Gagal membuat kandang');
|
|
}
|
|
};
|
|
|
|
const handleStartEdit = (kandang: Kandang) => {
|
|
setEditingId(kandang.id);
|
|
setEditName(kandang.name);
|
|
setEditDocInCount((kandang.docInCount || 0).toString());
|
|
};
|
|
|
|
const handleSaveEdit = async () => {
|
|
if (editingId === null || !editName.trim()) return;
|
|
try {
|
|
await apiClient.updateKandang(editingId, editName.trim());
|
|
const docCount = parseInt(editDocInCount, 10);
|
|
if (!isNaN(docCount) && docCount >= 0) {
|
|
await apiClient.setKandangDocInCount(editingId, cycleData.id, docCount);
|
|
}
|
|
setEditingId(null);
|
|
loadKandangs();
|
|
} catch (error) {
|
|
console.error('Failed to update kandang:', error);
|
|
alert('Gagal mengupdate kandang');
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: number, name: string) => {
|
|
if (!confirm(`Hapus "${name}"? Data mortalitas terkait juga akan terpengaruh.`)) return;
|
|
try {
|
|
await apiClient.deleteKandang(id);
|
|
loadKandangs();
|
|
} catch (error) {
|
|
console.error('Failed to delete kandang:', error);
|
|
alert('Gagal menghapus kandang');
|
|
}
|
|
};
|
|
|
|
const totalDocInCount = kandangs.reduce((sum, k) => sum + (k.docInCount || 0), 0);
|
|
|
|
return (
|
|
<main className="px-4 sm:px-6 lg:px-8 pb-8">
|
|
<div className="mb-6">
|
|
<h1 className="text-3xl font-bold text-gray-800">Manajemen Kandang</h1>
|
|
<p className="mt-1 text-gray-600">
|
|
Kelola kandang dan populasi awal (DOC In) untuk setiap siklus produksi
|
|
</p>
|
|
</div>
|
|
|
|
{/* Current Cycle Indicator */}
|
|
<div className="mb-6 bg-gradient-to-r from-red-50 to-orange-50 border-l-4 border-red-600 rounded-xl p-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="bg-red-600 text-white rounded-full w-10 h-10 flex items-center justify-center">
|
|
<i className="fa-solid fa-calendar-day"></i>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-600">Siklus Produksi Saat Ini</p>
|
|
<p className="text-lg font-bold text-gray-900">
|
|
{cycleData.id}{' '}
|
|
<span className="text-sm font-normal text-gray-600">
|
|
(Hari {cycleData.currentDay} dari {cycleData.totalDays})
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Summary Card */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
<div className="bg-white rounded-xl shadow-sm p-6 border-l-4 border-blue-500">
|
|
<p className="text-sm text-gray-600 mb-1">Jumlah Kandang</p>
|
|
<p className="text-2xl font-bold text-gray-800">{kandangs.length}</p>
|
|
</div>
|
|
<div className="bg-white rounded-xl shadow-sm p-6 border-l-4 border-green-500">
|
|
<p className="text-sm text-gray-600 mb-1">
|
|
Total DOC In <span className="text-xs">(Siklus {cycleData.id})</span>
|
|
</p>
|
|
<p className="text-2xl font-bold text-green-600">
|
|
{totalDocInCount.toLocaleString()}{' '}
|
|
<span className="text-sm font-normal text-gray-500">ekor</span>
|
|
</p>
|
|
</div>
|
|
<div className="bg-white rounded-xl shadow-sm p-6 border-l-4 border-orange-500">
|
|
<p className="text-sm text-gray-600 mb-1">Rata-rata per Kandang</p>
|
|
<p className="text-2xl font-bold text-orange-600">
|
|
{kandangs.length > 0
|
|
? Math.floor(totalDocInCount / kandangs.length).toLocaleString()
|
|
: 0}{' '}
|
|
<span className="text-sm font-normal text-gray-500">ekor</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Add button */}
|
|
{!isAdding && (
|
|
<div className="mb-6">
|
|
<button
|
|
onClick={() => setIsAdding(true)}
|
|
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors font-medium"
|
|
>
|
|
<i className="fa-solid fa-plus mr-2"></i>
|
|
Tambah Kandang
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Add form */}
|
|
{isAdding && (
|
|
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<h3 className="text-lg font-semibold text-gray-800 mb-4">Tambah Kandang Baru</h3>
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
<div className="flex-1">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Nama Kandang</label>
|
|
<input
|
|
type="text"
|
|
value={newName}
|
|
onChange={(e) => setNewName(e.target.value)}
|
|
placeholder="Contoh: Kandang 2"
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500"
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
<div className="flex-1">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
DOC In (Siklus {cycleData.id})
|
|
</label>
|
|
<input
|
|
type="number"
|
|
value={newDocInCount}
|
|
onChange={(e) => setNewDocInCount(e.target.value)}
|
|
placeholder="Contoh: 10000"
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500"
|
|
/>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
<i className="fa-solid fa-info-circle mr-1"></i>
|
|
Nilai ini khusus untuk siklus produksi ini
|
|
</p>
|
|
</div>
|
|
<div className="flex items-end gap-2">
|
|
<button
|
|
onClick={handleAdd}
|
|
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
|
|
>
|
|
<i className="fa-solid fa-check mr-1"></i> Simpan
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
setIsAdding(false);
|
|
setNewName('');
|
|
setNewDocInCount('');
|
|
}}
|
|
className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors font-medium"
|
|
>
|
|
Batal
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Kandang Table */}
|
|
<div className="bg-white rounded-xl shadow-sm overflow-hidden">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
|
|
#
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
|
|
Nama Kandang
|
|
</th>
|
|
<th className="px-6 py-4 text-right text-xs font-semibold text-gray-700 uppercase tracking-wider">
|
|
DOC (Siklus Aktif)
|
|
</th>
|
|
<th className="px-6 py-4 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider">
|
|
Aksi
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200">
|
|
{kandangs.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={4} className="px-6 py-12 text-center text-gray-500">
|
|
<i className="fa-solid fa-warehouse text-4xl mb-3 block text-gray-300"></i>
|
|
Belum ada kandang. Tambahkan kandang baru untuk mulai.
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
kandangs.map((kandang, index) => (
|
|
<tr key={kandang.id} className="hover:bg-gray-50 transition-colors">
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-red-100 text-red-800 font-bold text-sm">
|
|
{index + 1}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
{editingId === kandang.id ? (
|
|
<input
|
|
type="text"
|
|
value={editName}
|
|
onChange={(e) => setEditName(e.target.value)}
|
|
className="px-3 py-1 border border-blue-500 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
autoFocus
|
|
/>
|
|
) : (
|
|
<span className="text-lg font-semibold text-gray-800">{kandang.name}</span>
|
|
)}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-right">
|
|
{editingId === kandang.id ? (
|
|
<input
|
|
type="number"
|
|
value={editDocInCount}
|
|
onChange={(e) => setEditDocInCount(e.target.value)}
|
|
className="w-32 px-3 py-1 border border-blue-500 rounded-lg text-right focus:ring-2 focus:ring-blue-500"
|
|
/>
|
|
) : (
|
|
<span className="text-lg font-semibold text-green-600">
|
|
{(kandang.docInCount || 0).toLocaleString()}
|
|
<span className="text-xs ml-1 text-gray-500">ekor</span>
|
|
</span>
|
|
)}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-center">
|
|
{editingId === kandang.id ? (
|
|
<div className="flex justify-center gap-2">
|
|
<button
|
|
onClick={handleSaveEdit}
|
|
className="px-3 py-1 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium"
|
|
>
|
|
<i className="fa-solid fa-check"></i>
|
|
</button>
|
|
<button
|
|
onClick={() => setEditingId(null)}
|
|
className="px-3 py-1 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors text-sm font-medium"
|
|
>
|
|
<i className="fa-solid fa-times"></i>
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="flex justify-center gap-2">
|
|
<button
|
|
onClick={() => handleStartEdit(kandang)}
|
|
className="px-3 py-1 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium"
|
|
>
|
|
<i className="fa-solid fa-edit mr-1"></i> Edit
|
|
</button>
|
|
<button
|
|
onClick={() => handleDelete(kandang.id, kandang.name)}
|
|
className="px-3 py-1 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium"
|
|
>
|
|
<i className="fa-solid fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
{kandangs.length > 0 && (
|
|
<tfoot className="bg-gray-50 border-t-2 border-gray-300">
|
|
<tr>
|
|
<td className="px-6 py-4" colSpan={2}>
|
|
<span className="font-bold text-gray-700">Total</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-right">
|
|
<span className="text-lg font-bold text-gray-800">
|
|
{totalDocInCount.toLocaleString()}
|
|
<span className="text-xs ml-1 text-gray-500">ekor</span>
|
|
</span>
|
|
</td>
|
|
<td></td>
|
|
</tr>
|
|
</tfoot>
|
|
)}
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<div className="mt-6 bg-blue-50 border-l-4 border-blue-500 rounded-lg p-6">
|
|
<div className="flex items-start">
|
|
<div className="flex-shrink-0">
|
|
<i className="fa-solid fa-info-circle text-blue-500 text-2xl"></i>
|
|
</div>
|
|
<div className="ml-4">
|
|
<h3 className="text-sm font-semibold text-blue-800 mb-2">Informasi Penting</h3>
|
|
<ul className="text-sm text-blue-700 space-y-1">
|
|
<li>
|
|
<i className="fa-solid fa-circle text-xs mr-2"></i>
|
|
<strong>DOC In Count diatur per kandang untuk setiap siklus</strong> - ketika
|
|
membuat siklus baru, Anda perlu mengatur ulang DOC In untuk semua kandang
|
|
</li>
|
|
<li>
|
|
<i className="fa-solid fa-circle text-xs mr-2"></i>Data mortalitas di "Manajemen
|
|
Data" akan dipisahkan per kandang
|
|
</li>
|
|
<li>
|
|
<i className="fa-solid fa-circle text-xs mr-2"></i>Dashboard akan menampilkan data
|
|
per kandang dan total gabungan
|
|
</li>
|
|
<li>
|
|
<i className="fa-solid fa-circle text-xs mr-2"></i>Menghapus kandang akan
|
|
mempengaruhi semua data historis yang terkait
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
};
|
|
|
|
export default KandangManagementPage;
|