import Button from '@/components/Button'; import Card from '@/components/Card'; import Dropdown from '@/components/Dropdown'; import { OptionType } from '@/components/input/SelectInput'; import Menu from '@/components/menu/Menu'; import MenuItem from '@/components/menu/MenuItem'; import { formatNumber } from '@/lib/helper'; import { Dashboard, DashboardOverviewCharts, DashboardComparisonCharts, DashboardChartsSeries, DashboardChartsDataset, } from '@/types/api/dashboard/dashboard'; import { Icon } from '@iconify/react'; import { useState, useEffect } from 'react'; import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; type DashboardLineChartProps = { analysisMode: 'OVERVIEW' | 'COMPARISON'; data: Dashboard; selectedKandang?: OptionType; }; // Type guard to check if charts is DashboardOverviewCharts function isOverviewCharts( charts: DashboardOverviewCharts | DashboardComparisonCharts | undefined ): charts is DashboardOverviewCharts { if (!charts) return false; return ( 'deplesi' in charts || 'body_weight' in charts || 'fcr' in charts || 'performance' in charts || 'quality_control' in charts ); } // Type guard to check if charts is DashboardComparisonCharts function isComparisonCharts( charts: DashboardOverviewCharts | DashboardComparisonCharts | undefined ): charts is DashboardComparisonCharts { if (!charts) return false; return 'farm' in charts || 'flock' in charts || 'kandang' in charts; } const lineColors: Record = { body_weight: '#10B981', std_body_weight: '#10B981', act_laying: '#1062B9', std_laying: '#1062B9', act_egg_weight: '#10B981', std_egg_weight: '#10B981', act_feed_intake: '#F52419', std_feed_intake: '#F52419', act_uniformity: '#F59E0B', std_uniformity: '#F59E0B', act_fcr: '#10B981', std_fcr: '#10B981', act_fcr_cum: '#F52419', std_fcr_cum: '#10B981', normal: '#10B981', abnormal: '#F52419', act_deplesi: '#10B981', std_deplesi: '#10B981', }; const defaultLineColors: string[] = [ '#10B981', '#1062B9', '#F52419', '#F59E0B', '#7F56D9', ]; // Helper function to get line color const getLineColor = ( seriesId: string | number, index: number, mode: 'OVERVIEW' | 'COMPARISON' ): string => { // For COMPARISON mode, use default colors with cycling if (mode === 'COMPARISON') { return defaultLineColors[index % defaultLineColors.length]; } // For OVERVIEW mode, use predefined colors or fallback to default const predefinedColor = lineColors[seriesId]; if (predefinedColor) { return predefinedColor; } // Fallback to default colors with cycling return defaultLineColors[index % defaultLineColors.length]; }; const DashboardLineChart = ({ analysisMode, data, selectedKandang, }: DashboardLineChartProps) => { const [chartData, setChartData] = useState('body_weight'); const [open, setOpen] = useState(false); // Track which series are visible (by series id) const [visibleSeries, setVisibleSeries] = useState>( new Set() ); // Mapping for chart type labels const chartTypeLabels: Record = { body_weight: 'Body Weight', performance: 'Performance', fcr: 'FCR', quality_control: 'Quality Control', deplesi: 'Deplesi', }; // Initialize all series as visible when chartData changes useEffect(() => { let seriesData: DashboardChartsSeries[] = []; if (analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts)) { seriesData = data.charts[chartData]?.series || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; seriesData = comparisonChart?.series || []; } // Set all series as visible by default const allSeriesIds = new Set(seriesData.map((s) => s.id)); setVisibleSeries(allSeriesIds); }, [chartData, analysisMode, data.charts]); return (
Performance{' '}
{analysisMode == 'OVERVIEW' && ( setOpen(!open)} > {chartTypeLabels[chartData]}{' '}
} className={{ content: 'w-52 mt-3', }} controlled={open} > { setChartData('body_weight'); setOpen(!open); }} /> { setChartData('performance'); setOpen(!open); }} /> { setChartData('fcr'); setOpen(!open); }} /> { setChartData('quality_control'); setOpen(!open); }} /> { setChartData('deplesi'); setOpen(!open); }} />
)}
{/* Legend - Dynamic based on series data */}
{(() => { // Get series data based on current mode and chartData let seriesData: DashboardChartsSeries[] = []; if (analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts)) { seriesData = data.charts[chartData]?.series || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; seriesData = comparisonChart?.series || []; } return seriesData.map((series, index) => { const isVisible = visibleSeries.has(series.id); const isStandard = series.id .toString() .toLowerCase() .includes('std'); return ( ); }); })()}
{/* Chart Container with Empty State Overlay */}
{/* Chart */} { // Transform data based on analysisMode if (analysisMode === 'OVERVIEW') { // For OVERVIEW mode, use the selected chart data if (isOverviewCharts(data.charts)) { const selectedChartData = data.charts[chartData]; if (!selectedChartData || !selectedChartData.dataset) return []; return selectedChartData.dataset; } return []; } else { // For COMPARISON mode, use the first available comparison chart if (isComparisonCharts(data.charts)) { const chartData = data.charts.farm || data.charts.flock || data.charts.kandang; if (!chartData || !chartData.dataset) return []; return chartData.dataset; } return []; } })()} margin={{ top: 5, right: 10, left: 0, bottom: 5, }} > { // Calculate dynamic domain based on visible data let seriesData: DashboardChartsSeries[] = []; let dataset: DashboardChartsDataset[] = []; if ( analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts) ) { seriesData = data.charts[chartData]?.series || []; dataset = data.charts[chartData]?.dataset || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; seriesData = comparisonChart?.series || []; dataset = comparisonChart?.dataset || []; } // Get all values from visible series const visibleSeriesIds = Array.from(visibleSeries); const allValues: number[] = []; dataset.forEach((item: DashboardChartsDataset) => { visibleSeriesIds.forEach((seriesId) => { const value = item[seriesId]; if (typeof value === 'number') { allValues.push(value); } }); }); if (allValues.length === 0) return [0, 100]; const minValue = Math.min(...allValues); const maxValue = Math.max(...allValues); // Add padding (10% on each side) const padding = (maxValue - minValue) * 0.1; const domainMin = Math.floor(Math.max(0, minValue - padding)); const domainMax = Math.ceil(maxValue + padding); return [domainMin, domainMax]; })()} ticks={(() => { // Calculate dynamic ticks based on domain let seriesData: DashboardChartsSeries[] = []; let dataset: DashboardChartsDataset[] = []; if ( analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts) ) { seriesData = data.charts[chartData]?.series || []; dataset = data.charts[chartData]?.dataset || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; seriesData = comparisonChart?.series || []; dataset = comparisonChart?.dataset || []; } const visibleSeriesIds = Array.from(visibleSeries); const allValues: number[] = []; dataset.forEach((item: DashboardChartsDataset) => { visibleSeriesIds.forEach((seriesId) => { const value = item[seriesId]; if (typeof value === 'number') { allValues.push(value); } }); }); if (allValues.length === 0) return [0, 25, 50, 75, 100]; const minValue = Math.min(...allValues); const maxValue = Math.max(...allValues); const padding = (maxValue - minValue) * 0.1; const domainMin = Math.floor(Math.max(0, minValue - padding)); const domainMax = Math.ceil(maxValue + padding); // Generate 5 evenly spaced ticks const range = domainMax - domainMin; const step = range / 4; return [ domainMin, Math.round(domainMin + step), Math.round(domainMin + step * 2), Math.round(domainMin + step * 3), domainMax, ]; })()} /> `Week ${value}`} content={(props) => { return (

{analysisMode === 'OVERVIEW' ? selectedKandang ? selectedKandang.label || 'Overview Performance' : 'Overview Performance' : 'Comparison Performance'}

    {props.payload.map((item, index) => { if (item.name.startsWith('STD. ')) return null; // Get series data to find the unit let seriesData: DashboardChartsSeries[] = []; if ( analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts) ) { seriesData = data.charts[chartData]?.series || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; seriesData = comparisonChart?.series || []; } // Find the series that matches this line's name const series = seriesData.find( (s) => s.label === item.name ); const color = series?.id ? getLineColor(series.id, index, analysisMode) : '#9ca3af'; const unit = series?.unit; return (
  • {formatNumber(item.value)} {unit}
    {item.name}
  • ); })}

Week {props.label}

); }} formatter={( value: number | undefined, name: string | undefined ) => { if ( value === undefined || name === undefined || name.startsWith('STD. ') ) return [undefined, undefined]; // Get series data to find the unit let seriesData: DashboardChartsSeries[] = []; if ( analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts) ) { seriesData = data.charts[chartData]?.series || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; seriesData = comparisonChart?.series || []; } // Find the series that matches this line's name const series = seriesData.find((s) => s.label === name); const id = series?.id || ''; return [value, id]; }} /> {/* Dynamic Line rendering based on visible series */} {(() => { let seriesData: DashboardChartsSeries[] = []; if ( analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts) ) { seriesData = data.charts[chartData]?.series || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; seriesData = comparisonChart?.series || []; } return seriesData .filter((series) => visibleSeries.has(series.id)) .map((series, index) => { const isStandard = series.id .toString() .toLowerCase() .includes('std'); // Use series.id directly as dataKey to match dataset fields const dataKey = series.id.toString(); return ( ); }); })()}
{/* Empty State Overlay */} {(() => { // Get current dataset let dataset: DashboardChartsDataset[] = []; if (analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts)) { dataset = data.charts[chartData]?.dataset || []; } else if ( analysisMode === 'COMPARISON' && isComparisonCharts(data.charts) ) { const comparisonChart = data.charts.farm || data.charts.flock || data.charts.kandang; dataset = comparisonChart?.dataset || []; } // Show empty state if dataset is empty if (dataset.length === 0) { return (
{/* Chart icon */}
{/* Empty state text */}

Data Not Yet Available

Please change your filters to get the data.

); } return null; })()}
); }; export default DashboardLineChart;