120 lines
4.1 KiB
TypeScript
120 lines
4.1 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
CartesianGrid,
|
|
Line,
|
|
LineChart,
|
|
ResponsiveContainer,
|
|
Tooltip,
|
|
XAxis,
|
|
YAxis,
|
|
} from 'recharts';
|
|
import { formatNumber } from '../../utils/format.ts';
|
|
|
|
export type PopulationPoint = {
|
|
date: string;
|
|
population: number;
|
|
chickenCount?: number;
|
|
};
|
|
|
|
type Props = {
|
|
data: PopulationPoint[];
|
|
title?: string;
|
|
};
|
|
|
|
function yTick(value: number): string {
|
|
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}jt`;
|
|
if (value >= 1_000) return `${Math.round(value / 1_000)}rb`;
|
|
return `${value}`;
|
|
}
|
|
|
|
const PopulationTrendChart: React.FC<Props> = ({ data, title = 'Tren Populasi' }) => {
|
|
if (!data || data.length === 0) {
|
|
return (
|
|
<div className="bg-white p-6 rounded-xl shadow-sm">
|
|
<h3 className="text-lg font-bold text-gray-800 mb-4">{title}</h3>
|
|
<div className="h-64 flex items-center justify-center text-gray-400">
|
|
<div className="text-center">
|
|
<i className="fa-solid fa-chart-line text-4xl mb-2"></i>
|
|
<p>Tidak ada data tersedia</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white p-6 rounded-xl shadow-sm">
|
|
<h3 className="text-lg font-bold text-gray-800 mb-2">{title}</h3>
|
|
|
|
{/* Legend: explicit color explanation */}
|
|
<div className="flex flex-wrap items-center gap-x-5 gap-y-2 mb-3 text-xs">
|
|
<span className="inline-flex items-center gap-2">
|
|
<span className="inline-block w-5 h-[3px] rounded-full bg-red-500"></span>
|
|
<span className="font-semibold text-red-700">Populasi (Stock Akhir Manual)</span>
|
|
</span>
|
|
<span className="inline-flex items-center gap-2">
|
|
<span className="inline-block w-5 h-[3px] rounded-full bg-blue-500"></span>
|
|
<span className="font-semibold text-blue-700">Chicken Counting (IOT)</span>
|
|
</span>
|
|
</div>
|
|
|
|
<div className="h-64">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<LineChart data={data} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#e0e0e0" />
|
|
<XAxis
|
|
dataKey="date"
|
|
fontSize={11}
|
|
tick={{ fill: '#6b7280' }}
|
|
interval="preserveStartEnd"
|
|
/>
|
|
<YAxis fontSize={11} tick={{ fill: '#6b7280' }} tickFormatter={yTick} />
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
border: '1px solid #e0e0e0',
|
|
borderRadius: '8px',
|
|
padding: '8px 12px',
|
|
}}
|
|
formatter={(value, name) => {
|
|
const num = Number(value ?? 0);
|
|
return [num === 0 ? '0' : formatNumber(num, 0), name];
|
|
}}
|
|
labelStyle={{ fontWeight: 600, marginBottom: '4px' }}
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="population"
|
|
stroke="#ef4444"
|
|
strokeWidth={2.5}
|
|
name="Populasi (Stock Akhir Manual)"
|
|
dot={{ r: 3, fill: '#ef4444' }}
|
|
activeDot={{ r: 5, strokeWidth: 2, stroke: '#fff' }}
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="chickenCount"
|
|
stroke="#3b82f6"
|
|
strokeWidth={2.5}
|
|
name="Chicken Counting (IOT)"
|
|
dot={{ r: 3, fill: '#3b82f6' }}
|
|
activeDot={{ r: 5, strokeWidth: 2, stroke: '#fff' }}
|
|
connectNulls={false}
|
|
/>
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
<p className="mt-3 text-xs text-gray-500">
|
|
<i className="fa-solid fa-circle-info mr-1 text-gray-400"></i>
|
|
Garis <span className="font-semibold text-red-700">merah</span> = populasi hasil perhitungan
|
|
manual (stock akhir), garis <span className="font-semibold text-blue-700">biru</span> =
|
|
hasil hitung otomatis sensor IOT. Semakin berdekatan kedua garis, semakin akurat hitungan
|
|
IOT.
|
|
</p>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PopulationTrendChart;
|