refactor(FE-316): Extract Uniformity charts into components

This commit is contained in:
rstubryan
2025-12-23 18:52:05 +07:00
parent 5dd64b9907
commit 0774200aa5
3 changed files with 230 additions and 171 deletions
@@ -1,92 +1,25 @@
import React from 'react';
import {
Bar,
BarChart,
Cell,
Pie,
PieChart,
Rectangle,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import Card from '@/components/Card';
import { Icon } from '@iconify/react';
import { formatNumber } from '@/lib/helper';
import UniformityBarChart from './chart/UniformityBarChart';
import UniformityGaugeChart from './chart/UniformityGaugeChart';
interface Payload {
value?: number;
name?: string;
dataKey?: string | number;
interface BarChartData {
name: string;
uv: number;
}
interface CustomTooltipProps {
active?: boolean;
payload?: readonly Payload[];
label?: string | number;
}
interface GaugeChartProps {
interface GaugeChartData {
value: number;
label: string;
kandang?: string;
week?: string;
currentValue?: number;
totalValue?: number;
}
const GaugeChart: React.FC<GaugeChartProps> = ({ value, label }) => {
const numberOfSegments = 50;
const filledSegments = Math.round((value / 100) * numberOfSegments);
const data = Array.from({ length: numberOfSegments }, (_, index) => ({
name: index,
value: 1,
filled: index < filledSegments,
}));
const activeColor = '#1890ff';
const inactiveColor = '#f0f0f0';
return (
<div className='relative w-full h-full flex flex-col items-center justify-end'>
<ResponsiveContainer width='100%' height='100%'>
<PieChart>
<Pie
data={data}
cx='50%'
cy='70%'
startAngle={180}
endAngle={0}
innerRadius='75%'
outerRadius='100%'
paddingAngle={2}
dataKey='value'
stroke='none'
isAnimationActive={false}
>
{data.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.filled ? activeColor : inactiveColor}
/>
))}
</Pie>
</PieChart>
</ResponsiveContainer>
<div className='absolute inset-x-0 bottom-8 flex flex-col items-center justify-center pointer-events-none'>
<span className='2xl:text-4xl text-2xl font-bold text-gray-800 mb-4'>
{value}%
</span>
<div className='mt-2 px-4 py-1 bg-gray-100 rounded-full shadow-sm border border-gray-200'>
<span className='text-sm font-medium text-gray-700 mb-32'>
{label}
</span>
</div>
</div>
</div>
);
};
const UniformityChart = () => {
const data = [
// TODO: Replace with actual API call
const barChartData: BarChartData[] = [
{
name: '48-52',
uv: 80,
@@ -133,113 +66,40 @@ const UniformityChart = () => {
},
];
const margin = {
top: 20,
right: 30,
left: 20,
bottom: 5,
// TODO: Replace with actual API call
const gaugeChartData: GaugeChartData = {
value: 52,
label: 'Uniformity',
kandang: 'Kandang Cirangga',
week: 'Week 2',
currentValue: 512,
totalValue: 1024,
};
function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
if (active && payload && payload.length && label !== undefined) {
const labelStr = String(label);
return (
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'>
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
<div className='flex items-center gap-2 mt-2 justify-between'>
<div className='flex items-center gap-2'>
<div className='w-5 h-5 bg-[#0069E0] rounded-md'></div>
{payload[0].value} of Birds
</div>
<span>{labelStr}</span>
</div>
</div>
);
}
return null;
}
return (
<section className='w-full grid grid-cols-1 xl:grid-cols-4 gap-4'>
<section className='w-full grid grid-cols-1 2xl:grid-cols-4 gap-4'>
<Card
title='Performance Overview'
variant='bordered'
className={{ wrapper: 'xl:col-span-3 w-full', body: 'h-96' }}
>
<div className='w-full h-full flex items-center justify-center'>
<ResponsiveContainer width='100%' height={300}>
<BarChart data={data} margin={margin} barGap={20}>
<XAxis dataKey='name' />
<YAxis />
<Tooltip
content={CustomTooltip}
wrapperStyle={{
width: '200px',
}}
/>
<Bar
dataKey='uv'
fill='#FFFFFF'
stroke='#DDD'
strokeWidth={2}
radius={[25, 25, 0, 0]}
activeBar={
<Rectangle
fill='#0069E0'
stroke='#0069E0'
radius={[25, 25, 0, 0]}
/>
}
/>
</BarChart>
</ResponsiveContainer>
<UniformityBarChart data={barChartData} />
</div>
</Card>
<Card
variant='bordered'
title='Weekly Performance'
className={{ wrapper: 'xl:col-span-1 w-full' }}
className={{ wrapper: 'xl:col-span-1 w-full', body: 'p-4' }}
>
<div className='flex flex-col'>
<div className='h-64 w-full relative flex justify-center'>
<GaugeChart value={52} label='Uniformity' />
</div>
<Card
variant='bordered'
className={{
wrapper: 'w-full',
}}
>
<section className='flex items-center gap-4'>
<div className='w-12 h-12 bg-base-200 rounded-lg flex items-center justify-center border border-gray-200 shrink-0'>
<Icon
icon='heroicons:calendar-date-range'
width={24}
height={24}
/>
</div>
<div className='grid grid-cols-1 min-w-0'>
<div className='flex items-center space-x-2 text-gray-500 text-sm mb-1'>
<span className='font-medium truncate'>Kandang Cirangga</span>
<span className='shrink-0'></span>
<span className='text-blue-600 font-semibold truncate'>
Week 2
</span>
</div>
<div className='text-xl font-bold text-gray-800'>
<span className='text-blue-600 break-all'>
{formatNumber(512)}
</span>
<span className='mx-1 text-gray-400 font-normal'>From</span>
<span className='text-gray-500 break-all'>
{formatNumber(1024)}
</span>
</div>
</div>
</section>
</Card>
</div>
<UniformityGaugeChart
value={gaugeChartData.value}
label={gaugeChartData.label}
kandang={gaugeChartData.kandang}
week={gaugeChartData.week}
currentValue={gaugeChartData.currentValue}
totalValue={gaugeChartData.totalValue}
/>
</Card>
</section>
);
@@ -0,0 +1,91 @@
import React from 'react';
import {
Bar,
BarChart,
Rectangle,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
interface Payload {
value?: number;
name?: string;
dataKey?: string | number;
}
interface CustomTooltipProps {
active?: boolean;
payload?: readonly Payload[];
label?: string | number;
}
interface BarChartData {
name: string;
uv: number;
}
interface UniformityBarChartProps {
data: BarChartData[];
}
function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
if (active && payload && payload.length && label !== undefined) {
const labelStr = String(label);
return (
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'>
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
<div className='flex items-center gap-2 mt-2 justify-between'>
<div className='flex items-center gap-2'>
<div className='w-5 h-5 bg-[#0069E0] rounded-md'></div>
{payload[0].value} of Birds
</div>
<span>{labelStr}</span>
</div>
</div>
);
}
return null;
}
const UniformityBarChart: React.FC<UniformityBarChartProps> = ({ data }) => {
const margin = {
top: 20,
right: 30,
left: 20,
bottom: 5,
};
return (
<ResponsiveContainer width='100%' height={300}>
<BarChart data={data} margin={margin} barGap={20}>
<XAxis dataKey='name' />
<YAxis />
<Tooltip
content={CustomTooltip}
wrapperStyle={{
width: '200px',
}}
/>
<Bar
dataKey='uv'
fill='#FFFFFF'
stroke='#DDD'
strokeWidth={2}
radius={[25, 25, 0, 0]}
activeBar={
<Rectangle
fill='#0069E0'
stroke='#0069E0'
radius={[25, 25, 0, 0]}
/>
}
/>
</BarChart>
</ResponsiveContainer>
);
};
export default UniformityBarChart;
@@ -0,0 +1,108 @@
import React from 'react';
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
import Card from '@/components/Card';
import { Icon } from '@iconify/react';
import { formatNumber } from '@/lib/helper';
interface UniformityGaugeChartProps {
value: number;
label: string;
kandang?: string;
week?: string;
currentValue?: number;
totalValue?: number;
}
const UniformityGaugeChart: React.FC<UniformityGaugeChartProps> = ({
value,
label,
kandang,
week,
currentValue,
totalValue,
}) => {
const numberOfSegments = 50;
const filledSegments = Math.round((value / 100) * numberOfSegments);
const data = Array.from({ length: numberOfSegments }, (_, index) => ({
name: index,
value: 1,
filled: index < filledSegments,
}));
const activeColor = '#1890ff';
const inactiveColor = '#f0f0f0';
return (
<div className='flex flex-col w-full'>
<div className='h-64 w-full relative flex justify-center'>
<div className='relative w-full h-full flex flex-col items-center justify-end'>
<ResponsiveContainer width='100%' height='100%'>
<PieChart>
<Pie
data={data}
cx='50%'
cy='70%'
startAngle={180}
endAngle={0}
innerRadius='75%'
outerRadius='100%'
paddingAngle={2}
dataKey='value'
stroke='none'
isAnimationActive={false}
>
{data.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.filled ? activeColor : inactiveColor}
/>
))}
</Pie>
</PieChart>
</ResponsiveContainer>
<div className='absolute inset-x-0 bottom-8 flex flex-col items-center justify-center'>
<span className='2xl:text-3xl text-2xl font-bold text-gray-800 mb-4'>
{value}%
</span>
<div className='mt-2 px-4 py-1 bg-base-100 rounded-full shadow-sm border border-gray-200'>
<span className='text-sm font-medium text-gray-700 mb-32'>
{label}
</span>
</div>
</div>
</div>
</div>
<Card
variant='bordered'
className={{
wrapper: 'w-full',
}}
>
<section className='flex items-center gap-4'>
<div className='w-12 h-12 bg-base-200 rounded-lg flex items-center justify-center border border-gray-200 shrink-0'>
<Icon icon='heroicons:calendar-date-range' width={24} height={24} />
</div>
<div className='grid grid-cols-1 min-w-0'>
<div className='flex items-center space-x-2 text-[#18181B80] text-sm mb-1'>
<span className='font-medium truncate'>{kandang}</span>
<span className='shrink-0'></span>
<span className='text-[#0069E0] font-semibold truncate'>
{week}
</span>
</div>
<div className='text-xl font-bold text-[#18181B80]'>
<span className='text-[#0069E0] break-all'>
{formatNumber(currentValue ?? 0)}
</span>
<span className='mx-1 text-gray-400 text-base'>From</span>
<span className='break-all'>{formatNumber(totalValue ?? 0)}</span>
</div>
</div>
</section>
</Card>
</div>
);
};
export default UniformityGaugeChart;