Files
lti-web-client/src/components/pages/dashboard/chart/ProductionLineChart.tsx
T

358 lines
9.2 KiB
TypeScript

'use client';
import { useState } from 'react';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts';
// Sample data in API format
const sampleApiData: ProductionChartItem[] = [
{
date: '2025-12-01T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 88 },
{ id: 2, name: 'Flock A-001', data: 92 },
{ id: 3, name: 'Flock B-001', data: 90 },
{ id: 4, name: 'Flock B-002', data: 85 },
],
},
{
date: '2025-12-03T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 85 },
{ id: 2, name: 'Flock A-001', data: 95 },
{ id: 3, name: 'Flock B-001', data: 93 },
{ id: 4, name: 'Flock B-002', data: 87 },
],
},
{
date: '2025-12-05T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 82 },
{ id: 2, name: 'Flock A-001', data: 98 },
{ id: 3, name: 'Flock B-001', data: 91 },
{ id: 4, name: 'Flock B-002', data: 84 },
],
},
{
date: '2025-12-07T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 80 },
{ id: 2, name: 'Flock A-001', data: 89 },
{ id: 3, name: 'Flock B-001', data: 88 },
{ id: 4, name: 'Flock B-002', data: 82 },
],
},
{
date: '2025-12-08T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 83 },
{ id: 2, name: 'Flock A-001', data: 92 },
{ id: 3, name: 'Flock B-001', data: 95 },
{ id: 4, name: 'Flock B-002', data: 85 },
],
},
{
date: '2025-12-11T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 81 },
{ id: 2, name: 'Flock A-001', data: 88 },
{ id: 3, name: 'Flock B-001', data: 92 },
{ id: 4, name: 'Flock B-002', data: 83 },
],
},
{
date: '2025-12-13T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 84 },
{ id: 2, name: 'Flock A-001', data: 90 },
{ id: 3, name: 'Flock B-001', data: 89 },
{ id: 4, name: 'Flock B-002', data: 86 },
],
},
{
date: '2025-12-15T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 82 },
{ id: 2, name: 'Flock A-001', data: 94 },
{ id: 3, name: 'Flock B-001', data: 96 },
{ id: 4, name: 'Flock B-002', data: 84 },
],
},
{
date: '2025-12-17T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 80 },
{ id: 2, name: 'Flock A-001', data: 91 },
{ id: 3, name: 'Flock B-001', data: 93 },
{ id: 4, name: 'Flock B-002', data: 82 },
],
},
{
date: '2025-12-19T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 79 },
{ id: 2, name: 'Flock A-001', data: 88 },
{ id: 3, name: 'Flock B-001', data: 90 },
{ id: 4, name: 'Flock B-002', data: 81 },
],
},
{
date: '2025-12-21T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 81 },
{ id: 2, name: 'Flock A-001', data: 97 },
{ id: 3, name: 'Flock B-001', data: 92 },
{ id: 4, name: 'Flock B-002', data: 83 },
],
},
{
date: '2025-12-23T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 83 },
{ id: 2, name: 'Flock A-001', data: 95 },
{ id: 3, name: 'Flock B-001', data: 98 },
{ id: 4, name: 'Flock B-002', data: 85 },
],
},
{
date: '2025-12-25T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 80 },
{ id: 2, name: 'Flock A-001', data: 89 },
{ id: 3, name: 'Flock B-001', data: 94 },
{ id: 4, name: 'Flock B-002', data: 82 },
],
},
{
date: '2025-12-27T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 82 },
{ id: 2, name: 'Flock A-001', data: 93 },
{ id: 3, name: 'Flock B-001', data: 96 },
{ id: 4, name: 'Flock B-002', data: 84 },
],
},
{
date: '2025-12-28T00:00:00Z',
flocks: [
{ id: 1, name: 'Flock A-002', data: 85 },
{ id: 2, name: 'Flock A-001', data: 96 },
{ id: 3, name: 'Flock B-001', data: 95 },
{ id: 4, name: 'Flock B-002', data: 87 },
],
},
];
// Helper function to format date based on period
const formatDateByPeriod = (
dateString: string,
period: 'daily' | 'weekly' | 'monthly' | 'yearly'
): string => {
const date = new Date(dateString);
const monthNames = [
'Jan',
'Feb',
'Mar',
'Apr',
'Mei',
'Jun',
'Jul',
'Agu',
'Sep',
'Okt',
'Nov',
'Des',
];
switch (period) {
case 'daily':
// Format: "1 Des"
return `${date.getDate()} ${monthNames[date.getMonth()]}`;
case 'weekly':
// Format: "Week 1 Des"
const weekNumber = Math.ceil(date.getDate() / 7);
return `Week ${weekNumber} ${monthNames[date.getMonth()]}`;
case 'monthly':
// Format: "Des"
return monthNames[date.getMonth()];
case 'yearly':
// Format: "2025"
return date.getFullYear().toString();
default:
return dateString;
}
};
// Type definitions for API data
interface FlockData {
id: number;
name: string;
data: number;
}
interface ProductionChartItem {
date: string;
flocks: FlockData[];
}
interface ProductionChartsData {
production_charts: ProductionChartItem[];
}
// Transform API data to Recharts format
const transformProductionData = (apiData: ProductionChartItem[]) => {
return apiData.map((item) => {
const transformed: Record<string, string | number> = {
date: item.date.split('T')[0], // Extract YYYY-MM-DD from ISO string
};
// Add each flock's data as a property
item.flocks.forEach((flock) => {
transformed[flock.name] = flock.data;
});
return transformed;
});
};
interface ProductionLineChartProps {
period?: 'daily' | 'weekly' | 'monthly' | 'yearly';
data?: ProductionChartItem[]; // Optional API data
}
const ProductionLineChart = ({
period = 'daily',
data: apiData,
}: ProductionLineChartProps) => {
// State to track which lines are hidden
const [hiddenLines, setHiddenLines] = useState<string[]>([]);
// Use API data if provided, otherwise use sample data
const chartData = apiData
? transformProductionData(apiData)
: transformProductionData(sampleApiData);
// Handle legend click to show/hide lines
const handleLegendClick = (dataKey: string) => {
setHiddenLines((prev) =>
prev.includes(dataKey)
? prev.filter((key) => key !== dataKey)
: [...prev, dataKey]
);
};
return (
<div className='w-full h-full'>
<h3 className='text-lg font-semibold mb-4'>
Performa Produksi per Flock
</h3>
<ResponsiveContainer width='100%' height={400}>
<LineChart
data={chartData}
margin={{
top: 5,
right: 30,
left: 0,
bottom: 5,
}}
>
<CartesianGrid strokeDasharray='3 3' stroke='#e5e7eb' />
<XAxis
dataKey='date'
tick={{ fontSize: 12 }}
tickLine={false}
axisLine={{ stroke: '#e5e7eb' }}
tickFormatter={(value) => formatDateByPeriod(value, period)}
/>
<YAxis
tick={{ fontSize: 12 }}
tickLine={false}
axisLine={{ stroke: '#e5e7eb' }}
domain={[0, 100]}
label={{
value: 'Persentase (%)',
angle: -90,
position: 'insideLeft',
style: { fontSize: 12 },
}}
/>
<Tooltip
contentStyle={{
backgroundColor: 'white',
border: '1px solid #e5e7eb',
borderRadius: '8px',
padding: '8px 12px',
}}
labelFormatter={(value) =>
formatDateByPeriod(value as string, period)
}
/>
<Legend
wrapperStyle={{
paddingTop: '20px',
}}
iconType='circle'
onClick={(e) => {
if (e.dataKey) handleLegendClick(e.dataKey as string);
}}
style={{ cursor: 'pointer' }}
/>
<Line
type='monotone'
dataKey='Flock A-002'
stroke='#3b82f6'
strokeWidth={2}
dot={{ r: 4, fill: '#3b82f6' }}
activeDot={{ r: 6 }}
hide={hiddenLines.includes('Flock A-002')}
/>
<Line
type='monotone'
dataKey='Flock A-001'
stroke='#10b981'
strokeWidth={2}
dot={{ r: 4, fill: '#10b981' }}
activeDot={{ r: 6 }}
hide={hiddenLines.includes('Flock A-001')}
/>
<Line
type='monotone'
dataKey='Flock B-001'
stroke='#f59e0b'
strokeWidth={2}
dot={{ r: 4, fill: '#f59e0b' }}
activeDot={{ r: 6 }}
hide={hiddenLines.includes('Flock B-001')}
/>
<Line
type='monotone'
dataKey='Flock B-002'
stroke='#ef4444'
strokeWidth={2}
dot={{ r: 4, fill: '#ef4444' }}
activeDot={{ r: 6 }}
hide={hiddenLines.includes('Flock B-002')}
/>
</LineChart>
</ResponsiveContainer>
</div>
);
};
export default ProductionLineChart;
// Export types for external use
export type { FlockData, ProductionChartItem, ProductionChartsData };