91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
import React from 'react';
|
|
import type { LatestAvg } from '../../types/api.ts';
|
|
import { formatDateId, formatNumber } from '../../utils/format.ts';
|
|
import {
|
|
adgStatus,
|
|
formatWeight,
|
|
getTargetWeightForAge,
|
|
type WeightStatus,
|
|
uniformityStatus,
|
|
weightDeviationStatus,
|
|
} from '../../utils/weight.ts';
|
|
|
|
const statusText: Record<WeightStatus, string> = {
|
|
ok: 'text-green-600',
|
|
warning: 'text-yellow-600',
|
|
critical: 'text-red-600',
|
|
};
|
|
|
|
const StatItem: React.FC<{
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
value: string;
|
|
status: WeightStatus;
|
|
}> = ({ icon, label, value, status }) => (
|
|
<div className="flex items-center p-2 rounded-lg hover:bg-gray-50">
|
|
<div className="text-2xl text-gray-400 w-12">{icon}</div>
|
|
<div>
|
|
<p className="text-sm text-gray-600 font-medium">{label}</p>
|
|
<p className={`text-2xl font-bold ${statusText[status]}`}>
|
|
<span className="text-base font-medium">{value}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
interface WeightStatsProps {
|
|
latest: LatestAvg | null;
|
|
docWeight: number;
|
|
}
|
|
|
|
const WeightStats: React.FC<WeightStatsProps> = ({ latest, docWeight }) => {
|
|
const target = latest ? getTargetWeightForAge(latest.age, docWeight) : null;
|
|
|
|
return (
|
|
<div className="bg-white p-6 rounded-xl shadow-sm h-full">
|
|
<div className="mb-4">
|
|
<h2 className="text-xl font-bold">Statistik Utama Pertumbuhan</h2>
|
|
{latest?.date ? (
|
|
<p className="text-sm text-gray-500 mt-1">Data per: {formatDateId(latest.date)}</p>
|
|
) : null}
|
|
</div>
|
|
{!latest ? (
|
|
<div className="flex flex-col items-center justify-center h-48 text-center">
|
|
<i className="fa-solid fa-exclamation-triangle text-5xl text-yellow-500 mb-4"></i>
|
|
<h3 className="text-lg font-semibold text-gray-700 mb-2">Data Tidak Tersedia</h3>
|
|
<p className="text-sm text-gray-500 max-w-xs">
|
|
Belum ada data timbangan untuk siklus ini. Data akan muncul setelah ayam ditimbang.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
<StatItem
|
|
icon={<i className="fa-solid fa-weight-scale"></i>}
|
|
label="Rata-rata Berat Ayam"
|
|
value={formatWeight(latest.average_weight, { stripTrailingZeros: true })}
|
|
status={weightDeviationStatus(latest.average_weight, target)}
|
|
/>
|
|
<StatItem
|
|
icon={<i className="fa-solid fa-chart-line"></i>}
|
|
label="Pertambahan Bobot Harian (ADG)"
|
|
value={formatWeight(latest.average_daily_gain, {
|
|
suffix: '/hari',
|
|
decimals: 2,
|
|
truncate: true,
|
|
})}
|
|
status={adgStatus(latest.average_daily_gain)}
|
|
/>
|
|
<StatItem
|
|
icon={<i className="fa-solid fa-percent"></i>}
|
|
label="Tingkat Keseragaman (Uniformity)"
|
|
value={`${formatNumber(latest.uniformity, 2, 2, true)} %`}
|
|
status={uniformityStatus(latest.uniformity)}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default WeightStats;
|