104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
import React from 'react';
|
|
import type { ChickenWeight } from '../../types/api.ts';
|
|
import { formatDateId, formatNumber } from '../../utils/format.ts';
|
|
import { formatWeight, getTargetWeightForAge } from '../../utils/weight.ts';
|
|
|
|
interface DailyDetailPanelProps {
|
|
row: ChickenWeight | null;
|
|
docWeight: number;
|
|
}
|
|
|
|
const DetailCard: React.FC<{
|
|
label: string;
|
|
value: string;
|
|
icon: React.ReactNode;
|
|
highlight?: boolean;
|
|
}> = ({ label, value, icon, highlight = false }) => (
|
|
<div
|
|
className={`bg-white rounded-xl shadow-sm p-4 ${
|
|
highlight ? 'border-2 border-red-500' : 'border border-gray-100'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="text-2xl text-red-500 flex-shrink-0">{icon}</div>
|
|
<div className="min-w-0">
|
|
<p className="text-xs text-gray-500">{label}</p>
|
|
<p className="text-xl font-bold text-gray-800 truncate">{value}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const DailyDetailPanel: React.FC<DailyDetailPanelProps> = ({ row, docWeight }) => {
|
|
if (!row) {
|
|
return (
|
|
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<div className="flex flex-col items-center justify-center py-6 text-center">
|
|
<i className="fa-solid fa-calendar-xmark text-4xl text-gray-300 mb-3"></i>
|
|
<p className="text-gray-500 font-medium">Belum ada data untuk tanggal ini</p>
|
|
<p className="text-gray-400 text-sm mt-1">
|
|
Pilih tanggal lain untuk melihat rincian harian
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const target = getTargetWeightForAge(row.age, docWeight);
|
|
const deviation = target != null && row.average_weight > 0 ? row.average_weight - target : null;
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
<DetailCard
|
|
label="Tanggal"
|
|
value={formatDateId(row.date)}
|
|
icon={<i className="fa-solid fa-calendar-day"></i>}
|
|
/>
|
|
<DetailCard
|
|
label="Umur"
|
|
value={`${row.age} hari`}
|
|
icon={<i className="fa-solid fa-clock"></i>}
|
|
/>
|
|
<DetailCard
|
|
label="DOC"
|
|
value={formatWeight(row.doc_weight, { decimals: 0 })}
|
|
icon={<i className="fa-solid fa-egg"></i>}
|
|
/>
|
|
<DetailCard
|
|
label="AVG Berat"
|
|
value={formatWeight(row.average_weight, { stripTrailingZeros: true })}
|
|
icon={<i className="fa-solid fa-weight-scale"></i>}
|
|
highlight
|
|
/>
|
|
<DetailCard
|
|
label="Uniformity"
|
|
value={`${formatNumber(row.uniformity, 2, 2, true)}%`}
|
|
icon={<i className="fa-solid fa-percent"></i>}
|
|
/>
|
|
<DetailCard
|
|
label="ADG"
|
|
value={`${formatNumber(row.average_daily_gain, 2, 2, true)} g/hari`}
|
|
icon={<i className="fa-solid fa-chart-line"></i>}
|
|
/>
|
|
<DetailCard
|
|
label="Populasi"
|
|
value={`${formatNumber(row.chicken_count, 0)} ekor`}
|
|
icon={<i className="fa-solid fa-users"></i>}
|
|
/>
|
|
{deviation != null && (
|
|
<DetailCard
|
|
label="Deviasi vs Target"
|
|
value={`${deviation >= 0 ? '+' : ''}${formatWeight(Math.abs(deviation), {
|
|
stripTrailingZeros: true,
|
|
})}`}
|
|
icon={<i className="fa-solid fa-arrow-trend-up"></i>}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DailyDetailPanel;
|