fix(FE): refactor UI Dashboard pixel perfect figma

This commit is contained in:
randy-ar
2026-01-28 17:54:55 +07:00
parent b19340536a
commit 34f93f8dcc
13 changed files with 716 additions and 249 deletions
@@ -151,7 +151,7 @@ const DashboardLineChart = ({
}}
variant='bordered'
>
<div className='flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6'>
<div className='flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-[12px]'>
<div className='text-lg font-semibold'>
Performance{' '}
<Icon
@@ -169,22 +169,24 @@ const DashboardLineChart = ({
<Button
variant='outline'
color='none'
className='text-neutral-500 hover:text-neutral-700 rounded-lg px-4 py-2 border-neutral-300'
className='py-[10px] pl-[12px] pr-[4px] text-[#18181B]/50 rounded-[8px] text-[14px] border-[#18181B]/10 shadow-button-soft'
onClick={() => setOpen(!open)}
>
{chartTypeLabels[chartData]}{' '}
<div className='divider divider-horizontal p-0 m-0 before:bg-neutral-300 after:bg-neutral-300'></div>
<Icon icon='heroicons:chevron-down' width={20} height={20} />
<div className='w-[26px] h-[20px] flex items-center justify-center border-l border-[#18181B]/10'>
<Icon icon='heroicons:chevron-down' width={14} height={14} />
</div>
</Button>
}
className={{
content: 'w-52 mt-3',
content: '',
}}
controlled={open}
>
<Menu>
<MenuItem
title='Body weight'
className='text-sm padding-[12px] whitespace-nowrap'
onClick={() => {
setChartData('body_weight');
setOpen(!open);
@@ -192,6 +194,7 @@ const DashboardLineChart = ({
/>
<MenuItem
title='Performance'
className='text-sm padding-[12px] whitespace-nowrap'
onClick={() => {
setChartData('performance');
setOpen(!open);
@@ -199,6 +202,7 @@ const DashboardLineChart = ({
/>
<MenuItem
title='FCR'
className='text-sm padding-[12px] whitespace-nowrap'
onClick={() => {
setChartData('fcr');
setOpen(!open);
@@ -206,6 +210,7 @@ const DashboardLineChart = ({
/>
<MenuItem
title='Quality Control'
className='text-sm padding-[12px] whitespace-nowrap'
onClick={() => {
setChartData('quality_control');
setOpen(!open);
@@ -213,6 +218,7 @@ const DashboardLineChart = ({
/>
<MenuItem
title='Deplesi'
className='text-sm padding-[12px] whitespace-nowrap'
onClick={() => {
setChartData('deplesi');
setOpen(!open);
@@ -248,8 +254,8 @@ const DashboardLineChart = ({
.includes('std');
return (
<button
key={series.id}
<Button
key={`${series.id}-${index}`}
onClick={() => {
const newVisible = new Set(visibleSeries);
if (isVisible) {
@@ -259,14 +265,16 @@ const DashboardLineChart = ({
}
setVisibleSeries(newVisible);
}}
className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors ${
variant='outline'
color='none'
className={`flex items-center gap-[8px] px-[12px] py-[10px] rounded-[8px] border transition-colors ${
isVisible
? 'border-neutral-400 bg-neutral-50'
: 'border-neutral-300 hover:bg-neutral-50'
? 'border-[#18181B]/10 hover:bg-[#18181B]/4'
: 'border-[#18181B]/10 bg-[#18181B]/4'
}`}
>
<div
className={`w-6 h-0.5 ${
className={`w-[20px] h-[2px] ${
isStandard ? 'border-t-2 border-dashed' : ''
} ${!isVisible ? 'opacity-30' : ''}`}
style={{
@@ -279,17 +287,17 @@ const DashboardLineChart = ({
}}
></div>
<span
className={`text-sm ${isVisible ? 'text-neutral-900 font-medium' : 'text-neutral-700'}`}
className={`font-semibold text-[14px] ${isVisible ? 'text-[#18181B]/50' : 'text-[#18181B]/50'}`}
>
{series.label}
</span>
<Icon
icon='heroicons:information-circle'
icon='heroicons:eye'
width={16}
height={16}
className='text-neutral-400'
className='text-[#18181B]/40'
/>
</button>
</Button>
);
});
})()}
@@ -335,20 +343,54 @@ const DashboardLineChart = ({
<CartesianGrid strokeDasharray='3 3' stroke='#e5e7eb' />
<XAxis
dataKey='week'
tick={{ fontSize: 11, fill: '#9ca3af' }}
tick={{
fontSize: 12,
fill: '#18181B',
opacity: 0.5,
fontWeight: 600,
}}
tickLine={false}
axisLine={{ stroke: '#e5e7eb' }}
axisLine={{ stroke: '#C1C1C180', opacity: 0.5 }}
label={{
value: 'Weeks',
position: 'insideBottom',
offset: -5,
style: { fontSize: 12, fill: '#9ca3af' },
style: {
fontSize: 12,
fill: '#18181B',
opacity: 0.2,
fontWeight: 600,
},
}}
/>
<YAxis
tick={{ fontSize: 11, fill: '#9ca3af' }}
tick={{
fontSize: 12,
fill: '#18181B',
opacity: 0.5,
fontWeight: 600,
}}
label={
chartData === 'body_weight' || chartData === 'performance'
? {
value:
chartData === 'body_weight'
? 'Body Weight'
: 'Percentage',
position: 'insideLeft',
angle: -90,
offset: 25,
style: {
fontSize: 12,
fill: '#18181B',
opacity: 0.2,
fontWeight: 600,
},
}
: undefined
}
tickLine={false}
axisLine={{ stroke: '#e5e7eb' }}
axisLine={{ stroke: '#C1C1C180', opacity: 0.5 }}
domain={(() => {
// Calculate dynamic domain based on visible data
let seriesData: DashboardChartsSeries[] = [];
@@ -399,14 +441,12 @@ const DashboardLineChart = ({
})()}
ticks={(() => {
// Calculate dynamic ticks based on domain
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' &&
@@ -416,7 +456,6 @@ const DashboardLineChart = ({
data.charts.farm ||
data.charts.flock ||
data.charts.kandang;
seriesData = comparisonChart?.series || [];
dataset = comparisonChart?.dataset || [];
}
@@ -436,6 +475,20 @@ const DashboardLineChart = ({
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);
@@ -444,13 +497,16 @@ const DashboardLineChart = ({
const range = domainMax - domainMin;
const step = range / 4;
return [
// 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);
})()}
/>
<Tooltip
@@ -458,7 +514,7 @@ const DashboardLineChart = ({
backgroundColor: '#1f2937',
border: 'none',
borderRadius: '8px',
padding: '8px 12px',
padding: '12px 12px',
color: 'white',
}}
labelStyle={{ color: 'white', marginBottom: '4px' }}
@@ -466,8 +522,8 @@ const DashboardLineChart = ({
labelFormatter={(value) => `Week ${value}`}
content={(props) => {
return (
<div className='flex flex-col gap-2 rounded-lg bg-neutral-950 p-4 text-white'>
<p className='text-neutral-300 text-xs font-semibold text-start'>
<div className='flex flex-col gap-[6px] rounded-lg bg-neutral-950 p-4 text-white'>
<p className='text-neutral-300 text-[12px] font-semibold text-start'>
{analysisMode === 'OVERVIEW'
? selectedKandang
? selectedKandang.label || 'Overview Performance'
@@ -506,12 +562,12 @@ const DashboardLineChart = ({
return (
<li
key={item.name}
className='flex w-full justify-between items-center flex-row gap-6 p-0'
key={`${item.name}-${index}`}
className='flex w-full justify-between items-center flex-row gap-y-[6px] gap-x-[12px] p-0'
>
<span className='flex flex-row gap-1 items-center'>
<div
className='h-4 w-4 m-0 rounded-md'
className='h-[20px] w-[20px] m-0 rounded-[4px]'
style={{
backgroundColor: color,
}}
@@ -526,7 +582,7 @@ const DashboardLineChart = ({
);
})}
</ul>
<p className='text-neutral-300 text-xs text-start'>
<p className='text-neutral-300 text-[12px] text-start'>
Week {props.label}
</p>
</div>
@@ -598,7 +654,7 @@ const DashboardLineChart = ({
return (
<Line
key={series.id}
key={`${series.id}--${index}`}
type='monotone'
dataKey={dataKey}
name={series.label}
@@ -649,13 +705,15 @@ const DashboardLineChart = ({
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 h-12 bg-blue-500 rounded-xl flex items-center justify-center mb-4'>
<Icon
icon='heroicons:chart-bar'
className='text-white'
width={24}
height={24}
/>
<div className='w-[50px] h-[50px] bg-[var(--main-color-base-100,#FFFFFF)] border-[1px] border-[#18181B]/10 rounded-[14px] border border-[#18181B] shadow-[0px_25px_50px_-12px_#00000040] flex items-center justify-center mb-4'>
<div className='w-[38px] h-[38px] bg-[#0069E0] rounded-[8px] border-[1px] border-[#0069E0] 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 */}