mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +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