mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-316): Add Uniformity page components
This commit is contained in:
@@ -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;
|
||||||
Reference in New Issue
Block a user