Files
dashboard/components/CobbStandardPage.tsx
T
Alberto-Audrix 32a36cceff
CI / lint-and-test (push) Canceled after 0s
first commit
2026-07-28 08:55:05 +07:00

199 lines
8.2 KiB
TypeScript

import React, { useState, useMemo, useEffect } from 'react';
import { cobbStandardPerformance } from '../mockData/cobbStandard.ts';
import { useAppContext } from '../context/AppContext.tsx';
import { formatNumber, formatRatio } from '../utils/formatters.ts';
const CobbStandardPage: React.FC = () => {
const { cycleData } = useAppContext();
// Use chickInWeight from active cycle if available, otherwise default to 42
const [initialBw, setInitialBw] = useState<number>(cycleData.chickInWeight || 42);
// Use docInCount from active cycle if available, otherwise default to 50000
const [capacity, setCapacity] = useState<number>(cycleData.docInCount || 50000);
// Sync initialBw when cycle's chickInWeight changes
useEffect(() => {
if (cycleData.chickInWeight) {
setInitialBw(cycleData.chickInWeight);
}
}, [cycleData.chickInWeight]);
// Sync capacity when cycle's docInCount changes
useEffect(() => {
if (cycleData.docInCount) {
setCapacity(cycleData.docInCount);
}
}, [cycleData.docInCount]);
const calculatedData = useMemo(() => {
const results = [];
let lastBw = Number(initialBw);
let lastCumFeed = 0;
for (const standard of cobbStandardPerformance) {
const currentBw = lastBw * (1 + standard.dailyGainPercent);
const dailyGainG = currentBw - lastBw;
const cumFeedG = currentBw * standard.fcr;
const dailyFeedG = cumFeedG - lastCumFeed;
const feedKg = (dailyFeedG * capacity) / 1000;
const feedBag = feedKg / 50;
results.push({
day: standard.day,
bw: Math.round(currentBw),
dailyGainG: Math.round(dailyGainG),
dailyGainPercent: standard.dailyGainPercent,
dailyFeedG: Math.round(dailyFeedG),
cumFeedG: Math.round(cumFeedG),
fcr: standard.fcr,
feedKg: Math.round(feedKg),
feedBag: Math.round(feedBag),
});
lastBw = currentBw;
lastCumFeed = cumFeedG;
}
return results;
}, [initialBw, capacity]);
return (
<main className="px-4 sm:px-6 lg:px-8 pb-8">
<div className="flex items-center mb-6">
<i className="fa-solid fa-calculator text-3xl text-red-600 mr-4"></i>
<div>
<h1 className="text-3xl font-bold text-gray-800">Kalkulator Standar Performa</h1>
<p className="mt-1 text-gray-600">
Hitung proyeksi pertumbuhan dan kebutuhan pakan berdasarkan bobot awal dan kapasitas
kandang.
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-white p-4 rounded-xl shadow-sm">
<label htmlFor="initialBw" className="block text-sm font-medium text-gray-700">
Bobot Awal DOC (g)
</label>
<input
type="number"
id="initialBw"
value={initialBw}
onChange={(e) => setInitialBw(Number(e.target.value))}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 text-lg font-semibold"
/>
</div>
<div className="bg-white p-4 rounded-xl shadow-sm">
<label htmlFor="capacity" className="block text-sm font-medium text-gray-700">
Kapasitas Kandang (ekor)
</label>
<input
type="number"
id="capacity"
value={capacity}
onChange={(e) => setCapacity(Number(e.target.value))}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 text-lg font-semibold"
/>
</div>
</div>
<div className="bg-white p-4 rounded-xl shadow-sm overflow-hidden">
<div className="overflow-auto max-h-[calc(100vh-380px)]">
<table className="min-w-full border-collapse text-sm">
<thead className="bg-gray-700 text-white sticky top-0 z-10">
<tr>
<th
rowSpan={2}
className="p-2 border border-gray-800 font-semibold sticky left-0 bg-gray-700 z-20"
>
Umur
</th>
<th rowSpan={2} className="p-2 border border-gray-800 font-semibold">
BW (g)
</th>
<th colSpan={2} className="p-2 border border-gray-800 font-semibold">
Daily Gain
</th>
<th colSpan={2} className="p-2 border border-gray-800 font-semibold">
Feed Consumption
</th>
<th rowSpan={2} className="p-2 border border-gray-800 font-semibold">
FCR
</th>
<th colSpan={2} className="p-2 border border-gray-800 font-semibold">
Kebutuhan Pakan
</th>
</tr>
<tr>
<th className="p-2 border border-gray-800 font-semibold">(g)</th>
<th className="p-2 border border-gray-800 font-semibold">(%)</th>
<th className="p-2 border border-gray-800 font-semibold">Daily (g)</th>
<th className="p-2 border border-gray-800 font-semibold">Cum (g)</th>
<th className="p-2 border border-gray-800 font-semibold">(Kg)</th>
<th className="p-2 border border-gray-800 font-semibold">(Bag)</th>
</tr>
</thead>
<tbody className="font-mono">
<tr className="bg-yellow-100 font-bold">
<td className="p-1 border border-gray-200 text-center bg-gray-700 text-white sticky left-0 z-10">
0
</td>
<td className="p-1 border border-gray-200 text-center text-gray-700">
{initialBw}
</td>
<td className="p-1 border border-gray-200 text-center" colSpan={7}></td>
</tr>
{calculatedData.map((row) => {
const isSeventhDay = row.day > 0 && row.day % 7 === 0;
const rowClass = `hover:bg-red-50 ${isSeventhDay ? 'font-bold bg-blue-100' : 'even:bg-gray-100/50'}`;
const cellBorderClass = isSeventhDay
? 'border-b-[3px] border-double border-blue-400'
: 'border-gray-200';
return (
<tr key={row.day} className={rowClass}>
<td
className={`p-1 border ${cellBorderClass} text-center bg-gray-700 text-white font-semibold sticky left-0 z-10`}
>
{row.day}
</td>
<td
className={`p-1 border ${cellBorderClass} text-center font-bold text-red-600`}
>
{formatNumber(row.bw)}
</td>
<td className={`p-1 border ${cellBorderClass} text-center text-gray-700`}>
{formatNumber(row.dailyGainG)}
</td>
<td className={`p-1 border ${cellBorderClass} text-center text-gray-700`}>
{(row.dailyGainPercent * 100).toFixed(0)}%
</td>
<td className={`p-1 border ${cellBorderClass} text-center text-gray-700`}>
{formatNumber(row.dailyFeedG)}
</td>
<td className={`p-1 border ${cellBorderClass} text-center text-gray-700`}>
{formatNumber(row.cumFeedG)}
</td>
<td className={`p-1 border ${cellBorderClass} text-center text-gray-700`}>
{formatRatio(row.fcr)}
</td>
<td className={`p-1 border ${cellBorderClass} text-center text-gray-700`}>
{formatNumber(row.feedKg)}
</td>
<td className={`p-1 border ${cellBorderClass} text-center text-gray-700`}>
{formatNumber(row.feedBag)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
<div className="text-right mt-2 text-xs text-gray-500 font-semibold">
Standar Performa COBB (2018)
</div>
</main>
);
};
export default CobbStandardPage;