mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
753 lines
27 KiB
TypeScript
753 lines
27 KiB
TypeScript
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<string, string> = {
|
|
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<keyof DashboardOverviewCharts>('body_weight');
|
|
const [open, setOpen] = useState(false);
|
|
// Track which series are visible (by series id)
|
|
const [visibleSeries, setVisibleSeries] = useState<Set<string | number>>(
|
|
new Set()
|
|
);
|
|
|
|
// Mapping for chart type labels
|
|
const chartTypeLabels: Record<keyof DashboardOverviewCharts, string> = {
|
|
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 (
|
|
<Card
|
|
className={{
|
|
wrapper: 'w-full rounded-lg p-0',
|
|
body: 'p-4',
|
|
}}
|
|
variant='bordered'
|
|
>
|
|
<div className='flex flex-col sm:flex-row justify-between items-start gap-4 mb-3'>
|
|
<div className='text-lg font-semibold'>
|
|
Performance{' '}
|
|
<Icon
|
|
icon='heroicons:information-circle'
|
|
width={20}
|
|
height={20}
|
|
className='inline text-neutral-500'
|
|
/>
|
|
</div>
|
|
{analysisMode == 'OVERVIEW' && (
|
|
<Dropdown
|
|
align='end'
|
|
direction='bottom'
|
|
className={{
|
|
content: 'mt-1 min-w-full',
|
|
}}
|
|
trigger={
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
className='py-2.5 pl-3 pr-2 text-base-content/50 rounded-lg text-sm border-base-content/10 shadow-button-soft'
|
|
onClick={() => setOpen(!open)}
|
|
>
|
|
{chartTypeLabels[chartData]}{' '}
|
|
<div className='w-6 h-5 flex items-center justify-center border-l border-base-content/10'>
|
|
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
|
</div>
|
|
</Button>
|
|
}
|
|
controlled={open}
|
|
>
|
|
<Menu className='p-0 w-full shadow-button-soft border border-base-content/10 rounded-lg'>
|
|
<MenuItem
|
|
title='Body weight'
|
|
className='text-sm padding-3 whitespace-nowrap'
|
|
onClick={() => {
|
|
setChartData('body_weight');
|
|
setOpen(!open);
|
|
}}
|
|
/>
|
|
<MenuItem
|
|
title='Performance'
|
|
className='text-sm padding-3 whitespace-nowrap'
|
|
onClick={() => {
|
|
setChartData('performance');
|
|
setOpen(!open);
|
|
}}
|
|
/>
|
|
<MenuItem
|
|
title='FCR'
|
|
className='text-sm padding-3 whitespace-nowrap'
|
|
onClick={() => {
|
|
setChartData('fcr');
|
|
setOpen(!open);
|
|
}}
|
|
/>
|
|
<MenuItem
|
|
title='Quality Control'
|
|
className='text-sm padding-3 whitespace-nowrap'
|
|
onClick={() => {
|
|
setChartData('quality_control');
|
|
setOpen(!open);
|
|
}}
|
|
/>
|
|
<MenuItem
|
|
title='Deplesi'
|
|
className='text-sm padding-3 whitespace-nowrap'
|
|
onClick={() => {
|
|
setChartData('deplesi');
|
|
setOpen(!open);
|
|
}}
|
|
/>
|
|
</Menu>
|
|
</Dropdown>
|
|
)}
|
|
</div>
|
|
|
|
{/* Legend - Dynamic based on series data */}
|
|
<div className='flex flex-wrap gap-3 mb-6'>
|
|
{(() => {
|
|
// 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 (
|
|
<Button
|
|
key={`${series.id}-${index}`}
|
|
onClick={() => {
|
|
const newVisible = new Set(visibleSeries);
|
|
if (isVisible) {
|
|
newVisible.delete(series.id);
|
|
} else {
|
|
newVisible.add(series.id);
|
|
}
|
|
setVisibleSeries(newVisible);
|
|
}}
|
|
variant='outline'
|
|
color='none'
|
|
className={`flex items-center gap-2 p-3 rounded-lg border transition-colors ${
|
|
isVisible
|
|
? 'border-base-content/10 hover:bg-base-content/4'
|
|
: 'border-base-content/10 bg-base-content/4'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`w-5 h-0.5 ${
|
|
isStandard ? 'border-t-2 border-dashed' : ''
|
|
} ${!isVisible ? 'opacity-30' : ''}`}
|
|
style={{
|
|
backgroundColor: isStandard
|
|
? 'transparent'
|
|
: getLineColor(series.id, index, analysisMode),
|
|
borderColor: isStandard
|
|
? getLineColor(series.id, index, analysisMode)
|
|
: 'transparent',
|
|
}}
|
|
></div>
|
|
<span
|
|
className={`font-semibold text-sm ${isVisible ? 'text-base-content/50' : 'text-base-content/50'}`}
|
|
>
|
|
{series.label}
|
|
</span>
|
|
<Icon
|
|
icon='heroicons:eye'
|
|
width={16}
|
|
height={16}
|
|
className='text-base-content/40'
|
|
/>
|
|
</Button>
|
|
);
|
|
});
|
|
})()}
|
|
</div>
|
|
|
|
{/* Chart Container with Empty State Overlay */}
|
|
<div className='relative'>
|
|
{/* Chart */}
|
|
<ResponsiveContainer width='100%' height={350}>
|
|
<LineChart
|
|
data={(() => {
|
|
// 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,
|
|
}}
|
|
>
|
|
<CartesianGrid strokeDasharray='3 3' stroke='#e5e7eb' />
|
|
<XAxis
|
|
dataKey='week'
|
|
tick={{
|
|
fontSize: 12,
|
|
fill: '#18181B',
|
|
opacity: 0.5,
|
|
fontWeight: 600,
|
|
}}
|
|
tickLine={false}
|
|
axisLine={{ stroke: '#C1C1C180', opacity: 0.5 }}
|
|
label={{
|
|
value: 'Weeks',
|
|
position: 'insideBottom',
|
|
offset: -5,
|
|
style: {
|
|
fontSize: 12,
|
|
fill: '#18181B',
|
|
opacity: 0.2,
|
|
fontWeight: 600,
|
|
},
|
|
}}
|
|
/>
|
|
<YAxis
|
|
tick={{
|
|
fontSize: 12,
|
|
fill: '#18181B',
|
|
opacity: 0.5,
|
|
fontWeight: 600,
|
|
}}
|
|
label={
|
|
(chartData === 'body_weight' || chartData === 'performance') &&
|
|
analysisMode === 'OVERVIEW'
|
|
? {
|
|
value:
|
|
chartData === 'body_weight'
|
|
? 'Body Weight'
|
|
: 'Percentage',
|
|
position: 'insideLeft',
|
|
angle: -90,
|
|
offset: 5,
|
|
style: {
|
|
fontSize: 12,
|
|
fill: '#18181B',
|
|
opacity: 0.2,
|
|
fontWeight: 600,
|
|
},
|
|
}
|
|
: analysisMode === 'COMPARISON'
|
|
? {
|
|
value: 'Percentage',
|
|
position: 'insideLeft',
|
|
angle: -90,
|
|
offset: 5,
|
|
style: {
|
|
fontSize: 12,
|
|
fill: '#18181B',
|
|
opacity: 0.2,
|
|
fontWeight: 600,
|
|
},
|
|
}
|
|
: undefined
|
|
}
|
|
tickLine={false}
|
|
axisLine={{ stroke: '#C1C1C180', opacity: 0.5 }}
|
|
domain={(() => {
|
|
// 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 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 || [];
|
|
}
|
|
|
|
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);
|
|
|
|
// Handle edge case where min equals max
|
|
if (minValue === maxValue) {
|
|
const value = Math.round(minValue);
|
|
const padding = Math.max(10, Math.abs(value) * 0.2);
|
|
return [
|
|
Math.floor(value - padding),
|
|
Math.floor(value - padding / 2),
|
|
value,
|
|
Math.ceil(value + padding / 2),
|
|
Math.ceil(value + padding),
|
|
];
|
|
}
|
|
|
|
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;
|
|
|
|
// Use Set to ensure unique values
|
|
const tickSet = new Set([
|
|
domainMin,
|
|
Math.round(domainMin + step),
|
|
Math.round(domainMin + step * 2),
|
|
Math.round(domainMin + step * 3),
|
|
domainMax,
|
|
]);
|
|
|
|
return Array.from(tickSet).sort((a, b) => a - b);
|
|
})()}
|
|
tickFormatter={(value) => formatNumber(Number(value))}
|
|
/>
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: '#1f2937',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
padding: '12px 12px',
|
|
color: 'white',
|
|
}}
|
|
labelStyle={{ color: 'white', marginBottom: '4px' }}
|
|
itemStyle={{ color: 'white', fontSize: '12px' }}
|
|
labelFormatter={(value) => `Week ${value}`}
|
|
content={(props) => {
|
|
return (
|
|
<div className='flex flex-col gap-1.5 rounded-lg bg-neutral-950 p-4 text-white'>
|
|
<p className='text-white/50 text-xs font-semibold text-start'>
|
|
{analysisMode === 'OVERVIEW'
|
|
? selectedKandang
|
|
? selectedKandang.label || 'Overview Performance'
|
|
: 'Overview Performance'
|
|
: 'Comparison Performance'}
|
|
</p>
|
|
<ul className='flex flex-col gap-1'>
|
|
{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 (
|
|
<li
|
|
key={`${item.name}-${index}`}
|
|
className='flex w-full justify-between items-center flex-row gap-y-1.5 gap-x-3 p-0'
|
|
>
|
|
<span className='flex flex-row gap-1.5 items-center'>
|
|
<div
|
|
className='h-5 w-5 m-0 rounded'
|
|
style={{
|
|
backgroundColor: color,
|
|
}}
|
|
></div>
|
|
<div className='m-0'>
|
|
{formatNumber(item.value)}
|
|
{unit}
|
|
</div>
|
|
</span>
|
|
<span className='m-0'>{item.name}</span>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
<p className='text-white/50 text-xs text-start'>
|
|
Week {props.label}
|
|
</p>
|
|
</div>
|
|
);
|
|
}}
|
|
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 (
|
|
<Line
|
|
key={`${series.id}--${index}`}
|
|
type='monotone'
|
|
dataKey={dataKey}
|
|
name={series.label}
|
|
stroke={getLineColor(series.id, index, analysisMode)}
|
|
opacity={isStandard ? 0.5 : 1}
|
|
strokeWidth={2}
|
|
strokeDasharray={isStandard ? '5 5' : undefined}
|
|
dot={
|
|
isStandard
|
|
? false
|
|
: {
|
|
r: 3,
|
|
fill: '#fff',
|
|
stroke: getLineColor(
|
|
series.id,
|
|
index,
|
|
analysisMode
|
|
),
|
|
strokeWidth: 2,
|
|
}
|
|
}
|
|
activeDot={isStandard ? undefined : { r: 5 }}
|
|
/>
|
|
);
|
|
});
|
|
})()}
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
|
|
{/* 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 (
|
|
<div className='absolute inset-x-0 inset-y-15 z-10 flex flex-col items-center justify-center rounded-lg'>
|
|
{/* Chart icon */}
|
|
<div className='w-12.5 h-12.5 bg-[var(--main-color-base-100,#FFFFFF)] border border-base-content/10 rounded-[0.875rem] border border-base-content shadow-[0px_25px_50px_-12px_#00000040] flex items-center justify-center mb-2'>
|
|
<div className='w-9.5 h-9.5 bg-primary rounded-lg border border-primary flex items-center justify-center shadow-[inset_0px_4px_4px_0px_#FFFFFF80,inset_0px_2px_0px_0px_#FFFFFF80]'>
|
|
<Icon
|
|
icon='heroicons:chart-bar'
|
|
className='text-white'
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Empty state text */}
|
|
<h3 className='text-base-content/50 font-semibold text-sm mb-1'>
|
|
Data Not Yet Available
|
|
</h3>
|
|
<p className='text-base-content/50 text-xs text-center max-w-xs'>
|
|
Please change your filters to get the data.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
})()}
|
|
</div>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default DashboardLineChart;
|