import React, { useState, useMemo } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, } from 'recharts'; import { formatNumber, createYAxisFormatter } from '../../utils/formatters.ts'; interface PopulationDataPoint { date: string; population: number; } interface ChickenCountDataPoint { date: string; count: number | null; } interface KandangData { id: number; name: string; docInCount: number; } interface CombinedTrendChartProps { populationData: PopulationDataPoint[]; chickenCountData: ChickenCountDataPoint[]; kandangs: KandangData[]; cycleData: any; apiClient: any; selectedKandangId?: number | null; onKandangChange?: (kandangId: number | null) => void; hideKandangSelector?: boolean; } const KANDANG_COLORS = [ '#ef4444', '#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316', ]; const CombinedTrendChart: React.FC = ({ populationData, chickenCountData, kandangs, cycleData, apiClient, selectedKandangId: externalSelectedKandangId, onKandangChange, hideKandangSelector = false, }) => { const [internalSelectedKandangId, setInternalSelectedKandangId] = useState(null); // Use external state if provided, otherwise use internal state const selectedKandangId = externalSelectedKandangId !== undefined ? externalSelectedKandangId : internalSelectedKandangId; const _setSelectedKandangId = onKandangChange || setInternalSelectedKandangId; const [filteredPopulation, setFilteredPopulation] = useState(populationData); const [filteredChickenCount, setFilteredChickenCount] = useState(chickenCountData); const [loading, setLoading] = useState(false); // Helper: build population and chicken count data from records const buildChartData = async (records: any[], initPop: number, kandangId: number | null) => { const populations: PopulationDataPoint[] = []; const chickenCounts: ChickenCountDataPoint[] = []; const sortedRecords = [...records].sort((a: any, b: any) => a.day - b.day); const today = new Date(); const startDate = new Date(cycleData.startDate); today.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0); const startDateMs = startDate.getTime(); const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1); const currentDayOfCycle = Math.min( Math.floor((today.getTime() - startDateMs) / (1000 * 60 * 60 * 24)), lastCycleDay ); const recordMap = new Map(); sortedRecords.forEach((record: any) => { recordMap.set(record.day, record); }); // Fetch chicken counting data from IoT table const chickenCountMap: Record = {}; try { const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + currentDayOfCycle); const startDateStr = new Date(startDate).toISOString().split('T')[0]; const endDateStr = endDate.toISOString().split('T')[0]; const chickenCountData = await apiClient.getChickenCounting( startDateStr, endDateStr, kandangId || undefined ); chickenCountData.forEach((cc: any) => { // Parse date string in local timezone to avoid off-by-one errors const [year, month, day] = cc.date.split('T')[0].split('-').map(Number); const ccDate = new Date(year, month - 1, day); ccDate.setHours(0, 0, 0, 0); const dayNum = Math.floor((ccDate.getTime() - startDateMs) / (1000 * 60 * 60 * 24)); if (dayNum >= 0 && dayNum <= currentDayOfCycle) { chickenCountMap[dayNum] = (chickenCountMap[dayNum] || 0) + cc.totalCount; } }); } catch (err) { console.warn('Failed to load chicken counting data:', err); } let previousStockAkhir = 0; for (let day = 0; day <= currentDayOfCycle; day++) { const date = new Date(cycleData.startDate); date.setDate(date.getDate() + day); const formattedDate = date.toLocaleDateString('id-ID', { day: '2-digit', month: 'short' }); const record = recordMap.get(day); const mati = record?.mortalityCount || 0; const afkir = record?.afkir || 0; const panen = record?.panen || 0; // Stock calculation const stockAwal = day === 0 ? initPop : previousStockAkhir; const stockAkhir = stockAwal - mati - afkir - panen; populations.push({ date: formattedDate, population: stockAkhir }); chickenCounts.push({ date: formattedDate, count: chickenCountMap[day] !== undefined ? chickenCountMap[day]! : null, }); previousStockAkhir = stockAkhir; } return { populations, chickenCounts }; }; // Handle kandang selection const handleKandangChange = async (kandangId: number | null) => { if (onKandangChange) { onKandangChange(kandangId); } else { setInternalSelectedKandangId(kandangId); } setLoading(true); try { if (kandangId === null) { // "Semua Kandang" - use totals const allRecords = await apiClient.getMortalityTotals(cycleData.id); const totalInitPop = kandangs.reduce((sum, k) => sum + (k.docInCount || 0), 0); const { populations, chickenCounts } = await buildChartData(allRecords, totalInitPop, null); setFilteredPopulation(populations); setFilteredChickenCount(chickenCounts); } else { // Single kandang const kandangRecords = await apiClient.getMortalityRecords(cycleData.id, kandangId); const kandang = kandangs.find((k) => k.id === kandangId); const initPop = kandang?.docInCount || 0; const { populations, chickenCounts } = await buildChartData( kandangRecords, initPop, kandangId ); setFilteredPopulation(populations); setFilteredChickenCount(chickenCounts); } } catch (error) { console.error('Failed to fetch kandang data:', error); } finally { setLoading(false); } }; // Fetch proper data when kandangs are ready React.useEffect(() => { if (kandangs.length > 0 && selectedKandangId === null) { // Trigger initial data fetch to get chicken counting from IoT API handleKandangChange(null); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [kandangs.length]); // Sync chart data when external selectedKandangId changes React.useEffect(() => { if (externalSelectedKandangId !== undefined && kandangs.length > 0) { handleKandangChange(externalSelectedKandangId); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [externalSelectedKandangId]); // Merge population and chicken count data for chart const mergedData = useMemo(() => { return filteredPopulation.map((popPoint, index) => { const countPoint = filteredChickenCount[index]; return { date: popPoint.date, population: popPoint.population, chickenCount: countPoint?.count !== null ? countPoint?.count : undefined, }; }); }, [filteredPopulation, filteredChickenCount]); // Calculate domain for logarithmic scale const yAxisDomain = useMemo(() => { const allValues: number[] = []; mergedData.forEach((point) => { if (point.population && point.population > 0) allValues.push(point.population); if (point.chickenCount && point.chickenCount > 0) allValues.push(point.chickenCount); }); if (allValues.length === 0) return [1, 100000]; const dataMin = Math.min(...allValues); const dataMax = Math.max(...allValues); // For log scale, use multipliers instead of additive padding const domainMin = Math.max(1, Math.floor(dataMin * 0.7)); const domainMax = Math.ceil(dataMax * 1.3); return [domainMin, domainMax]; }, [mergedData]); // Show empty state if no data if (populationData.length === 0 && chickenCountData.length === 0) { return (

Tren Populasi & Chicken Counting

Tidak ada data

Belum ada data yang tersedia

); } return (

Tren Populasi & Chicken Counting

{/* Kandang filter buttons */} {!hideKandangSelector && kandangs.length > 0 && (
{kandangs.map((k, i) => ( ))}
)}
{loading ? (
) : (
{ if (value === null || value === undefined) return ['Tidak ada data', name]; return [formatNumber(value as number), name]; }} />
)}
); }; export default CombinedTrendChart;