mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
feat(FE-390): slicing UI and API integration for production dashboard
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
'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 };
|
||||
Reference in New Issue
Block a user