import React, { useState, useEffect, useMemo, useCallback } from 'react'; import CombinedTrendChart from './CombinedTrendChart.tsx'; import { useAppContext } from '../../context/AppContext.tsx'; import { apiClient } from '../../services/apiService.ts'; import { Kandang, MortalityRecord } from '../../types/counting.ts'; interface ChickenCountDataPoint { date: string; count: number | null; } interface PopulationDataPoint { date: string; population: number; } interface KandangPopulationData { name: string; data: PopulationDataPoint[]; color: string; } interface MortalitySourceRecord { day: number; mortalityCount?: number; afkir?: number; panen?: number; population?: number; chickenCount?: number; } const KANDANG_COLORS = [ '#ef4444', '#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316', ]; interface ChickenCountingDashboardProps { selectedKandangId: number | null; onKandangChange: (kandangId: number | null) => void; kandangs: Kandang[]; } const ChickenCountingDashboard: React.FC = ({ selectedKandangId, onKandangChange, kandangs, }) => { const { cycleData } = useAppContext(); const [chickenCountHistory, setChickenCountHistory] = useState([]); const [populationTrendHistory, setPopulationTrendHistory] = useState([]); const [_perKandangData, setPerKandangData] = useState([]); const [mortalityRecords, setMortalityRecords] = useState([]); const [perKandangMortalityRecords, setPerKandangMortalityRecords] = useState< Map >(new Map()); const [iotCurrentPopulation, setIotCurrentPopulation] = useState(null); // Fetch latest IOT Chicken Counting data (for selected kandang or all kandangs) useEffect(() => { const fetchLatestChickenCount = async () => { try { // Calculate date range for fetching latest data const today = new Date(); const startDate = new Date(cycleData.startDate); startDate.setHours(0, 0, 0, 0); today.setHours(23, 59, 59, 999); const startDateStr = startDate.toISOString().split('T')[0]; const todayStr = today.toISOString().split('T')[0]; // Fetch chicken counting data for selected kandang or all kandangs const chickenCounts = await apiClient.getChickenCounting( startDateStr, todayStr, selectedKandangId || undefined // Filter by kandang if one is selected ); if (chickenCounts && chickenCounts.length > 0) { // Get the most recent chicken count const sortedCounts = [...chickenCounts].sort( (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() ); const latestCount = sortedCounts[0].totalCount; setIotCurrentPopulation(latestCount); } else { // No IOT data available setIotCurrentPopulation(null); } } catch (error) { console.error('Failed to fetch latest chicken counting data:', error); setIotCurrentPopulation(null); } }; fetchLatestChickenCount(); // Refresh IOT data every 2 minutes const interval = setInterval(fetchLatestChickenCount, 2 * 60 * 1000); return () => clearInterval(interval); }, [cycleData.startDate, selectedKandangId, kandangs]); // Total initial population across all kandangs const totalInitialPopulation = useMemo(() => { const total = kandangs.reduce((sum, k) => sum + (k.docInCount || 0), 0); return total > 0 ? total : cycleData.docInCount || 20000; }, [kandangs, cycleData]); // Calculate statistics for the 5 boxes based on selected kandang const stats = useMemo(() => { let rawRecords: MortalitySourceRecord[]; let initialPop: number; if (selectedKandangId === null) { // Show all kandangs combined rawRecords = mortalityRecords; initialPop = totalInitialPopulation; } else { // Show specific kandang rawRecords = perKandangMortalityRecords.get(selectedKandangId) || []; const selectedKandang = kandangs.find((k) => k.id === selectedKandangId); initialPop = selectedKandang?.docInCount || 0; } // Calculate cumulative mortality and stock from raw records (same logic as DataManagementPage) let cumulativeMortality = 0; let cumulativeAfkir = 0; let cumulativePanen = 0; rawRecords.forEach((record) => { cumulativeMortality += record.mortalityCount || 0; cumulativeAfkir += record.afkir || 0; cumulativePanen += record.panen || 0; }); const totalMortality = cumulativeMortality + cumulativeAfkir; const totalPanen = cumulativePanen; const mortalityRate = initialPop > 0 ? ((totalMortality / initialPop) * 100).toFixed(2) : '0.00'; // Use IOT Chicken Counting data for all views (Semua Kandang or individual kandang) // Fall back to calculated population only when IOT data is unavailable let currentPopulation: number; if (iotCurrentPopulation !== null) { // Use latest IOT chicken count detection currentPopulation = iotCurrentPopulation; } else { // Calculate from mortality records when IOT data unavailable currentPopulation = initialPop - cumulativeMortality - cumulativeAfkir - cumulativePanen; } return { totalMortality, currentPopulation, totalPanen, mortalityRate, initialPop }; }, [ mortalityRecords, totalInitialPopulation, selectedKandangId, perKandangMortalityRecords, kandangs, iotCurrentPopulation, ]); // Helper: build population data from records const buildPopulationData = useCallback( (records: MortalitySourceRecord[], initPop: number) => { const chickenCounts: ChickenCountDataPoint[] = []; const populations: PopulationDataPoint[] = []; const sortedRecords = [...records].sort((a, b) => 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 lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1); const currentDayOfCycle = Math.min( Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)), lastCycleDay ); const recordMap = new Map(); sortedRecords.forEach((record) => { recordMap.set(record.day, record); }); let cumulativeMortality = 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); if (record && record.chickenCount) { chickenCounts.push({ date: formattedDate, count: record.chickenCount }); } else { chickenCounts.push({ date: formattedDate, count: null }); } if (record) { cumulativeMortality += (record.mortalityCount || 0) + (record.afkir || 0); } const calculatedPopulation = initPop - cumulativeMortality; const populationValue = record?.population || calculatedPopulation; populations.push({ date: formattedDate, population: populationValue }); } return { chickenCounts, populations }; }, [cycleData.startDate, cycleData.totalDays] ); // Fetch all data: total + per-kandang useEffect(() => { const fetchData = async () => { try { // Total across all kandangs - use getMortalityTotals for aggregated data const allRecords = await apiClient.getMortalityTotals(cycleData.id); setMortalityRecords(allRecords); // Store for statistics calculation const { chickenCounts, populations } = buildPopulationData( allRecords, totalInitialPopulation ); setChickenCountHistory(chickenCounts); setPopulationTrendHistory(populations); // Per-kandang data if (kandangs.length > 0) { const perKandang: KandangPopulationData[] = []; const mortalityMap = new Map(); for (let i = 0; i < kandangs.length; i++) { const k = kandangs[i]; if (!k) continue; try { const kRecords = await apiClient.getMortalityRecords(cycleData.id, k.id); mortalityMap.set(k.id, kRecords); // Store mortality records for each kandang const kInitPop = k.docInCount || 0; const { populations: kPop } = buildPopulationData(kRecords, kInitPop); perKandang.push({ name: k.name || `Kandang ${i + 1}`, data: kPop, color: KANDANG_COLORS[i % KANDANG_COLORS.length] || '#ef4444', }); } catch { // Skip if error fetching individual kandang } } setPerKandangData(perKandang); setPerKandangMortalityRecords(mortalityMap); } else { setPerKandangData([]); setPerKandangMortalityRecords(new Map()); } } catch (error) { console.error('Failed to fetch mortality records:', error); } }; fetchData(); }, [cycleData, kandangs, totalInitialPopulation, buildPopulationData]); return (
{/* Header */}

Dashboard Penghitungan Ayam

Pemantauan populasi dari berbagai kandang secara real-time

{/* Statistics Summary Cards */}
{/* Populasi Awal */}

Populasi Awal

{stats.initialPop.toLocaleString()}

{/* Populasi Saat Ini */}

Populasi Saat Ini

{stats.currentPopulation.toLocaleString()}

{/* Total Mortalitas */}

Total Mortalitas

{stats.totalMortality.toLocaleString()}

{/* Total Panen */}

Total Panen

{stats.totalPanen.toLocaleString()}

{/* Tingkat Mortalitas */}

Tingkat Mortalitas

{stats.mortalityRate}%

{/* Chart */} ({ id: k.id, name: k.name || '', docInCount: k.docInCount || 0, }))} cycleData={cycleData} apiClient={apiClient} selectedKandangId={selectedKandangId} onKandangChange={onKandangChange} hideKandangSelector={true} />
); }; export default ChickenCountingDashboard;