mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
refactor(FE-438): Add uniformity details preview drawer
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import { useMemo, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
@@ -8,10 +8,13 @@ import Button from '@/components/Button';
|
|||||||
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
|
import Tooltip from '@/components/Tooltip';
|
||||||
import { type OptionType } from '@/components/input/SelectInput';
|
import { type OptionType } from '@/components/input/SelectInput';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { UniformityDetail as UniformityDetailType } from '@/types/api/uniformity/uniformity';
|
import { UniformityDetail as UniformityDetailType } from '@/types/api/uniformity/uniformity';
|
||||||
import { formatDate } from '@/lib/helper';
|
import { formatDate } from '@/lib/helper';
|
||||||
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
|
import UniformityDetailsPreview from './UniformityDetailsPreview';
|
||||||
|
|
||||||
const statusColorMap: Record<string, string> = {
|
const statusColorMap: Record<string, string> = {
|
||||||
APPROVED: 'bg-[#00D39033]',
|
APPROVED: 'bg-[#00D39033]',
|
||||||
@@ -43,7 +46,7 @@ const getStatusText = (status: string): string => {
|
|||||||
return statusTextMap[status] || status;
|
return statusTextMap[status] || status;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DetailOptionType = OptionType & {
|
export type DetailOptionType = OptionType & {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,6 +58,10 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
|||||||
initialValues,
|
initialValues,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const setExpandedDrawerOpen = useUiStore((s) => s.setExpandedDrawerOpen);
|
||||||
|
const setExpandedDrawerContent = useUiStore(
|
||||||
|
(s) => s.setExpandedDrawerContent
|
||||||
|
);
|
||||||
|
|
||||||
const handleApprove = () => {
|
const handleApprove = () => {
|
||||||
router.push(`/uniformity?action=approve&id=${initialValues.id}`);
|
router.push(`/uniformity?action=approve&id=${initialValues.id}`);
|
||||||
@@ -64,6 +71,28 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
|||||||
router.push(`/uniformity?action=reject&id=${initialValues.id}`);
|
router.push(`/uniformity?action=reject&id=${initialValues.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleViewUniformityDetails = () => {
|
||||||
|
setExpandedDrawerContent(
|
||||||
|
<UniformityDetailsPreview
|
||||||
|
info_umum={initialValues.info_umum}
|
||||||
|
uniformityDetails={initialValues.uniformity_details}
|
||||||
|
sampling={initialValues.sampling}
|
||||||
|
result={initialValues.result}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setExpandedDrawerOpen(true);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setExpandedDrawerOpen(false);
|
||||||
|
setExpandedDrawerContent(null);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const infoUmumTableData: DetailOptionType[] = useMemo(() => {
|
const infoUmumTableData: DetailOptionType[] = useMemo(() => {
|
||||||
if (!initialValues) return [];
|
if (!initialValues) return [];
|
||||||
|
|
||||||
@@ -147,6 +176,22 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
|||||||
return <span>-</span>;
|
return <span>-</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id === 'file-name') {
|
||||||
|
return (
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<span>{valueMap[id]}</span>
|
||||||
|
<Tooltip content='Lihat Detail'>
|
||||||
|
<button
|
||||||
|
className='p-1 hover:bg-gray-100 rounded'
|
||||||
|
onClick={handleViewUniformityDetails}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:eye-outline' width={18} height={18} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return <span>{valueMap[id] || '-'}</span>;
|
return <span>{valueMap[id] || '-'}</span>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||||
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
|
import {
|
||||||
|
UniformityDetailItem,
|
||||||
|
UniformitySampling,
|
||||||
|
UniformityResult,
|
||||||
|
UniformityInfoUmum,
|
||||||
|
} from '@/types/api/uniformity/uniformity';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import Badge from '@/components/Badge';
|
||||||
|
import { formatNumber } from '@/lib/helper';
|
||||||
|
import { DetailOptionType } from '@/components/pages/uniformity/detail/UniformityDetail';
|
||||||
|
|
||||||
|
const weightStatusColorMap: Record<string, string> = {
|
||||||
|
ideal: 'bg-[#00D39033]',
|
||||||
|
outside: 'bg-error/10',
|
||||||
|
};
|
||||||
|
|
||||||
|
const weightStatusIndicatorColorMap: Record<string, string> = {
|
||||||
|
ideal: 'bg-[#008000]',
|
||||||
|
outside: 'bg-error',
|
||||||
|
};
|
||||||
|
|
||||||
|
const weightStatusTextMap: Record<string, string> = {
|
||||||
|
ideal: 'Ideal',
|
||||||
|
outside: 'Outside',
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWeightStatusColor = (status: string): string => {
|
||||||
|
return weightStatusColorMap[status] || 'bg-info';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWeightStatusIndicatorColor = (status: string): string => {
|
||||||
|
return weightStatusIndicatorColorMap[status] || 'bg-info';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWeightStatusText = (status: string): string => {
|
||||||
|
return weightStatusTextMap[status] || status;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BodyWeightData = {
|
||||||
|
id: string;
|
||||||
|
number: number;
|
||||||
|
weight: number;
|
||||||
|
status?: 'ideal' | 'outside';
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UniformityDetailsPreviewProps {
|
||||||
|
info_umum: UniformityInfoUmum;
|
||||||
|
uniformityDetails: UniformityDetailItem[];
|
||||||
|
sampling: UniformitySampling;
|
||||||
|
result: UniformityResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UniformityDetailsPreview = ({
|
||||||
|
info_umum,
|
||||||
|
uniformityDetails,
|
||||||
|
sampling,
|
||||||
|
result,
|
||||||
|
}: UniformityDetailsPreviewProps) => {
|
||||||
|
const setExpandedDrawerOpen = useUiStore((s) => s.setExpandedDrawerOpen);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setExpandedDrawerOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const samplingTableData: DetailOptionType[] = useMemo(() => {
|
||||||
|
if (!sampling) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'sampling-size',
|
||||||
|
label: 'Sampling size',
|
||||||
|
value: `${formatNumber(sampling.chick_qty_of_weight)} of Birds`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mean-weight',
|
||||||
|
label: 'Mean Weight',
|
||||||
|
value: `${sampling.mean_weight} g`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'min-limit',
|
||||||
|
label: 'Min Limit (-10%)',
|
||||||
|
value: `${sampling.mean_down} g`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'max-limit',
|
||||||
|
label: 'Max Limit (+10%)',
|
||||||
|
value: `${sampling.mean_up} g`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [sampling]);
|
||||||
|
|
||||||
|
const columnsSampling: ColumnDef<DetailOptionType>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'label',
|
||||||
|
header: 'Label',
|
||||||
|
cell: (props) => props.row.original.label,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'value',
|
||||||
|
header: 'Value',
|
||||||
|
cell: (props) => <span>{props.row.original.value}</span>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultTableData: DetailOptionType[] = useMemo(() => {
|
||||||
|
if (!result) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'ideal-birds',
|
||||||
|
label: 'Ideal Birds',
|
||||||
|
value: `${formatNumber(result.uniform_qty)} of Birds`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'outside-range',
|
||||||
|
label: 'Outside Range',
|
||||||
|
value: `${formatNumber(result.outside_qty)} of Birds`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uniformity',
|
||||||
|
label: 'Uniformity',
|
||||||
|
value: `${result.uniformity} %`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cv',
|
||||||
|
label: 'CV',
|
||||||
|
value: `${result.cv} %`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [result]);
|
||||||
|
|
||||||
|
const resultColumns: ColumnDef<DetailOptionType>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'label',
|
||||||
|
header: 'Label',
|
||||||
|
cell: (props) => props.row.original.label,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'value',
|
||||||
|
header: 'Value',
|
||||||
|
cell: (props) => <span>{props.row.original.value}</span>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableData = useMemo(() => {
|
||||||
|
if (!uniformityDetails) return [];
|
||||||
|
|
||||||
|
return uniformityDetails.map((detail, index) => ({
|
||||||
|
id: `body-weight-${index + 1}`,
|
||||||
|
number: index + 1,
|
||||||
|
weight: detail.weight,
|
||||||
|
status: detail.range.toLowerCase() as 'ideal' | 'outside',
|
||||||
|
}));
|
||||||
|
}, [uniformityDetails]);
|
||||||
|
|
||||||
|
const columnsUniformity: ColumnDef<BodyWeightData>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'number',
|
||||||
|
header: 'No',
|
||||||
|
cell: (props) => props.row.original.number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'weight',
|
||||||
|
header: 'Weight (g)',
|
||||||
|
cell: (props) => <span>{props.row.original.weight}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
|
header: 'Status',
|
||||||
|
cell: (props) => {
|
||||||
|
const status = props.row.original.status;
|
||||||
|
return status ? (
|
||||||
|
<div className='w-full'>
|
||||||
|
<Badge
|
||||||
|
statusIndicator={true}
|
||||||
|
variant='soft'
|
||||||
|
className={{
|
||||||
|
badge: `rounded-xl w-full justify-start border border-gray-200 text-black ${getWeightStatusColor(status)}`,
|
||||||
|
status: getWeightStatusIndicatorColor(status),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getWeightStatusText(status)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Badge
|
||||||
|
statusIndicator={true}
|
||||||
|
variant='soft'
|
||||||
|
className={{
|
||||||
|
badge:
|
||||||
|
'rounded-xl w-full justify-start border border-gray-200 text-black bg-info/10',
|
||||||
|
status: 'bg-info',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unknown
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className='w-full h-full bg-white border-l border-gray-200'>
|
||||||
|
{/* Header */}
|
||||||
|
<DrawerHeader
|
||||||
|
leftIcon=''
|
||||||
|
subtitle={info_umum?.file_name ?? 'Uniformity Details'}
|
||||||
|
subtitleClassName='text-sm text-neutral'
|
||||||
|
showDivider={false}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className='p-0 text-error hover:bg-transparent'
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:close' width={20} height={20} />
|
||||||
|
</button>
|
||||||
|
</DrawerHeader>
|
||||||
|
|
||||||
|
{/* Form Section */}
|
||||||
|
<div className='divider mt-3.5'></div>
|
||||||
|
<section className='w-full px-6'>
|
||||||
|
{uniformityDetails && uniformityDetails.length > 0 ? (
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
{/* Sampling and Range */}
|
||||||
|
<div className=''>
|
||||||
|
<p className='text-sm font-medium mb-5'>Sampling and Range</p>
|
||||||
|
<Table<DetailOptionType>
|
||||||
|
data={samplingTableData}
|
||||||
|
columns={columnsSampling}
|
||||||
|
pageSize={4}
|
||||||
|
className={{
|
||||||
|
containerClassName: 'mb-0',
|
||||||
|
paginationClassName: 'hidden',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Result */}
|
||||||
|
<div className=''>
|
||||||
|
<p className='text-sm font-medium mb-5'>Result</p>
|
||||||
|
<Table<DetailOptionType>
|
||||||
|
data={resultTableData}
|
||||||
|
columns={resultColumns}
|
||||||
|
pageSize={4}
|
||||||
|
className={{
|
||||||
|
containerClassName: 'mb-0',
|
||||||
|
paginationClassName: 'hidden',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body Weight Details */}
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Table<BodyWeightData>
|
||||||
|
data={tableData}
|
||||||
|
columns={columnsUniformity}
|
||||||
|
pageSize={15}
|
||||||
|
className={{ containerClassName: 'mb-5' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='flex flex-col items-center justify-center py-10 text-gray-400'>
|
||||||
|
<Icon
|
||||||
|
icon='mdi:file-document-outline'
|
||||||
|
width={64}
|
||||||
|
height={64}
|
||||||
|
className='mb-4'
|
||||||
|
/>
|
||||||
|
<p className='text-lg'>No data available</p>
|
||||||
|
<p className='text-sm'>Uniformity details not found</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UniformityDetailsPreview;
|
||||||
Reference in New Issue
Block a user