mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
358 lines
9.2 KiB
TypeScript
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 };
|