mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 23:05:46 +00:00
fix(FE): refactor UI Dashboard pixel perfect figma
This commit is contained in:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user