feat(FE-316): Add Uniformity page components

This commit is contained in:
rstubryan
2025-12-23 16:09:29 +07:00
parent 035482accc
commit 398282b3bf
4 changed files with 365 additions and 0 deletions
@@ -0,0 +1,164 @@
import {
Bar,
BarChart,
Rectangle,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import Card from '@/components/Card';
interface Payload {
value?: number;
name?: string;
dataKey?: string | number;
}
interface CustomTooltipProps {
active?: boolean;
payload?: readonly Payload[];
label?: string | number;
}
const UniformityChart = () => {
// #region Sample data
const data = [
{
name: '48-52',
uv: 80,
},
{
name: '52-56',
uv: 120,
},
{
name: '56-60',
uv: 160,
},
{
name: '60-64',
uv: 200,
},
{
name: '64-68',
uv: 160,
},
{
name: '68-72',
uv: 120,
},
{
name: '72-76',
uv: 80,
},
{
name: '76-80',
uv: 120,
},
{
name: '84-88',
uv: 160,
},
{
name: '88-92',
uv: 200,
},
{
name: '92-96',
uv: 160,
},
];
const margin = {
top: 20,
right: 30,
left: 20,
bottom: 5,
};
// #endregion
function getIntroOfPage(label: string): string {
if (label === 'Page A') {
return "Page A is about men's clothing";
}
if (label === 'Page B') {
return "Page B is about women's dress";
}
if (label === 'Page C') {
return "Page C is about women's bag";
}
if (label === 'Page D') {
return 'Page D is about household goods';
}
if (label === 'Page E') {
return 'Page E is about food';
}
if (label === 'Page F') {
return 'Page F is about baby food';
}
return '';
}
function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
if (active && payload && payload.length && label !== undefined) {
const labelStr = String(label);
return (
<div className='border border-[#d88488] bg-white p-2.5 rounded shadow-sm'>
<p className='m-0 font-bold'>{`${labelStr} : ${payload[0].value}`}</p>
<p className='m-0'>{getIntroOfPage(labelStr)}</p>
<p className='m-0 border-t border-dashed border-[#f5f5f5]'>
Anything you want can be displayed here.
</p>
</div>
);
}
return null;
}
return (
<section className='w-full grid grid-cols-1 xl: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} />
<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>
</div>
</Card>
<Card
variant='bordered'
title='Weekly Performance'
className={{ wrapper: 'xl:col-span-1 w-full' }}
>
<div className='h-80 flex items-center justify-center text-gray-400'>
Weekly Performance Content
</div>
</Card>
</section>
);
};
export default UniformityChart;
@@ -0,0 +1,59 @@
'use client';
import { usePathname, useRouter } from 'next/navigation';
import Drawer from '@/components/Drawer';
import React, { ReactNode } from 'react';
import UniformityTable from '@/components/pages/uniformity/UniformityTable';
import { useUiStore } from '@/stores/ui/ui.store';
export default function UniformityPageWrapper({
children,
}: {
children: ReactNode;
}) {
const pathname = usePathname();
const router = useRouter();
const toggleValidate = useUiStore((s) => s.toggleValidate);
const isAdd = pathname.includes('/add');
const isEdit = pathname.includes('/detail/edit');
const isDetail = pathname.includes('/detail');
const isOpen = isAdd || isEdit || isDetail;
const handleBackdropClick = () => {
const unsub = useUiStore.getState().subscribeIsValid((isValid) => {
if (isValid) {
router.push('/uniformity');
}
});
toggleValidate();
setTimeout(() => {
unsub?.();
}, 100);
};
return (
<>
<div className='w-full p-4'>
<UniformityTable
refresh={() => !isOpen && router.push('/uniformity')}
/>
</div>
<Drawer
open={isOpen}
setOpen={(v) => {
if (!v) router.push('/uniformity');
}}
closeOnBackdropClick={isDetail ? true : false}
onBackdropClick={handleBackdropClick}
variant='right'
zIndex='99999'
sidebarContent={isOpen && <div className=''>{children}</div>}
/>
</>
);
}
@@ -0,0 +1,93 @@
import Badge from '@/components/Badge';
import Card from '@/components/Card';
import { Icon } from '@iconify/react';
import { formatNumber } from '@/lib/helper';
const UniformityStat = () => {
const statisticsData = [
{
title: 'Total Population',
value: 1908978,
icon: 'heroicons-outline:inbox-stack',
change: '15.5%',
changeType: 'increase',
},
{
title: 'Total Uniformity',
value: 954489,
icon: 'heroicons-outline:scale',
change: '50%',
changeType: 'decrease',
},
{
title: 'Total Depletion',
value: 954489,
icon: 'heroicons-outline:inbox-stack',
change: '15.5%',
changeType: 'increase',
},
{
title: 'Total Production',
value: 2534,
icon: 'heroicons-outline:inbox-stack',
change: '15.5%',
changeType: 'increase',
},
];
return (
<section className='grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4'>
{statisticsData.map((stat, index) => (
<Card
key={index}
variant='bordered'
size='sm'
className={{
wrapper: 'w-full',
footer: 'bg-[#F8F8F8]',
}}
footer={
<>
<section className='flex items-center justify-between'>
<span className='font-normal text-gray-500'>
From last month
</span>
<Badge
color={stat.changeType === 'increase' ? 'success' : 'error'}
variant='soft'
className={{ badge: 'rounded-2xl' }}
>
<Icon
icon={
stat.changeType === 'increase'
? 'heroicons-outline:arrow-trending-up'
: 'heroicons-outline:arrow-trending-down'
}
width={16}
height={16}
className='inline-block'
/>
{stat.change}
</Badge>
</section>
</>
}
>
<div className='flex gap-2 items-center'>
<div className='p-2 border rounded-xl border-gray-300 shrink-0'>
<Icon icon={stat.icon} width={32} height={32} />
</div>
<div className='grid grid-cols-1 min-w-0'>
<span className='truncate'>{stat.title}</span>
<span className='text-xl font-semibold break-all'>
{formatNumber(stat.value)}
</span>
</div>
</div>
</Card>
))}
</section>
);
};
export default UniformityStat;
@@ -0,0 +1,49 @@
'use client';
import Button from '@/components/Button';
import UniformityChart from '@/components/pages/uniformity/UniformityChart';
import UniformityStat from '@/components/pages/uniformity/UniformityStat';
import { Icon } from '@iconify/react';
const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
return (
<>
<section className='[&_button]:w-full [&_button]:sm:w-fit [&_button]:last:mt-2 [&_button]:last:sm:mt-0 sm:flex sm:justify-between grid grid-cols-1 sm:gap-0 gap-2'>
<Button color='primary' href='/uniformity/add'>
<Icon icon='ic:round-plus' width={18} height={18} />
Add Uniformity
</Button>
<div className='sm:flex gap-2'>
<Button variant='outline'>
<Icon icon='heroicons:funnel' width={18} height={18} />
Filter
</Button>
<Button variant='outline'>
<Icon icon='heroicons:cloud-arrow-down' width={18} height={18} />
Export
</Button>
</div>
</section>
<div className='my-4 divider'></div>
<section>
<UniformityStat />
</section>
<div className='my-4'></div>
<section>
<UniformityChart />
</section>
<section>
<div>Uniformity Table Component</div>
</section>
</>
);
};
export default UniformityTable;