feat(FE-438): Add Uniformity detail view and navigation

This commit is contained in:
rstubryan
2025-12-28 20:58:59 +07:00
parent 9f0dc8c644
commit 8ec76af012
6 changed files with 366 additions and 4 deletions
@@ -1,6 +1,7 @@
'use client';
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import useSWR from 'swr';
import { Icon } from '@iconify/react';
import { ColumnDef, SortingState } from '@tanstack/react-table';
@@ -59,6 +60,7 @@ const isUniformityLocked = (uniformity: Uniformity): boolean => {
};
const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
const router = useRouter();
const isSuccess = useUniformityStore((s) => s.isSuccess);
const setIsSuccess = useUniformityStore((s) => s.setIsSuccess);
@@ -200,6 +202,14 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
setRowSelection({});
}, []);
const handleViewDetail = useCallback(
(uniformity: Uniformity) => {
router.push(`/uniformity/detail?uniformityId=${uniformity.id}`);
setRowSelection({});
},
[router]
);
const handleBulkApprove = useCallback(() => {
bulkApproveModal.openModal();
}, [bulkApproveModal]);
@@ -980,6 +990,19 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
{/* Floating Actions Button */}
<FloatingActionsButton
actions={[
{
action: 'DETAIL',
icon: 'mdi:eye-outline',
label: 'Lihat Detail',
hidden: selectedRowIds.length !== 1,
onClick() {
router.push(
`/uniformity/detail?uniformityId=${selectedRowIds[0]}`
);
setRowSelection({});
},
permissions: 'lti.production.uniformity.detail',
},
{
action: 'DELETE',
icon: 'mdi:delete-outline',
@@ -0,0 +1,283 @@
'use client';
import React, { useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { Icon } from '@iconify/react';
import { ColumnDef } from '@tanstack/react-table';
import Button from '@/components/Button';
import Tooltip from '@/components/Tooltip';
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
import Table from '@/components/Table';
import { formatNumber } from '@/lib/helper';
import { type OptionType } from '@/components/input/SelectInput';
import { UniformityDetail as UniformityDetailType } from '@/types/api/uniformity/uniformity';
type DetailOptionType = OptionType & {
id: string;
};
interface UniformityDetailProps {
initialValues: UniformityDetailType;
}
const UniformityDetail: React.FC<UniformityDetailProps> = ({
initialValues,
}) => {
const router = useRouter();
const handleClose = () => {
router.back();
};
const infoUmumTableData: DetailOptionType[] = useMemo(() => {
if (!initialValues) return [];
return [
{
id: 'tanggal',
value: 'tanggal',
label: 'Tanggal',
},
{
id: 'lokasi-farm',
value: 'lokasi-farm',
label: 'Lokasi Farm',
},
{
id: 'project-flock',
value: 'project-flock',
label: 'Project Flock',
},
{
id: 'kandang',
value: 'kandang',
label: 'Kandang',
},
{
id: 'file-name',
value: 'file-name',
label: 'File Name',
},
];
}, [initialValues]);
const columnsInfoUmum: ColumnDef<DetailOptionType>[] = useMemo(
() => [
{
accessorKey: 'label',
header: 'Label',
cell: (props) => props.row.original.label,
},
{
accessorKey: 'value',
header: 'Value',
cell: (props) => {
const id = props.row.original.id;
const { info_umum } = initialValues!;
const valueMap: Record<string, string> = {
tanggal: info_umum.tanggal,
'lokasi-farm': info_umum.lokasi_farm,
'project-flock': info_umum.project_flock,
kandang: info_umum.kandang,
'file-name': info_umum.file_name,
};
return <span>{valueMap[id] || '-'}</span>;
},
},
],
[initialValues]
);
const samplingTableData: DetailOptionType[] = useMemo(() => {
if (!initialValues) return [];
return [
{
id: 'sampling-size',
value: 'sampling-size',
label: 'Sampling size',
},
{
id: 'mean-weight',
value: 'mean-weight',
label: 'Mean Weight',
},
{
id: 'min-limit',
value: 'min-limit',
label: 'Min Limit (-10%)',
},
{
id: 'max-limit',
value: 'max-limit',
label: 'Max Limit (+10%)',
},
];
}, [initialValues]);
const columnsSampling: ColumnDef<DetailOptionType>[] = useMemo(
() => [
{
accessorKey: 'label',
header: 'Label',
cell: (props) => props.row.original.label,
},
{
accessorKey: 'value',
header: 'Value',
cell: (props) => {
const id = props.row.original.id;
const { sampling } = initialValues!;
const valueMap: Record<string, string> = {
'sampling-size': `${formatNumber(sampling.chick_qty_of_weight)} of Birds`,
'mean-weight': `${formatNumber(sampling.mean_weight)} g`,
'min-limit': `${formatNumber(sampling.mean_down)} g`,
'max-limit': `${formatNumber(sampling.mean_up)} g`,
};
return <span>{valueMap[id] || '-'}</span>;
},
},
],
[initialValues]
);
const resultTableData: DetailOptionType[] = useMemo(() => {
if (!initialValues) return [];
return [
{
id: 'ideal-birds',
value: 'ideal-birds',
label: 'Ideal Birds',
},
{
id: 'outside-range',
value: 'outside-range',
label: 'Outside Range',
},
{
id: 'uniformity',
value: 'uniformity',
label: 'Uniformity',
},
{
id: 'cv',
value: 'cv',
label: 'CV',
},
];
}, [initialValues]);
const columnsResult: ColumnDef<DetailOptionType>[] = useMemo(
() => [
{
accessorKey: 'label',
header: 'Label',
cell: (props) => props.row.original.label,
},
{
accessorKey: 'value',
header: 'Value',
cell: (props) => {
const id = props.row.original.id;
const { result } = initialValues!;
const valueMap: Record<string, string> = {
'ideal-birds': `${formatNumber(result.uniform_qty)} of Birds`,
'outside-range': `${formatNumber(result.outside_qty)} of Birds`,
uniformity: `${result.uniformity} %`,
cv: `${result.cv}`,
};
return <span>{valueMap[id] || '-'}</span>;
},
},
],
[initialValues]
);
return (
<section className='w-full h-full bg-white border-l border-gray-200'>
{/* Header */}
<DrawerHeader
leftIcon=''
subtitle={`${initialValues?.info_umum.file_name || ''}`}
subtitleClassName='text-sm text-neutral'
showDivider={false}
>
<Button variant='link' className='p-0 text-error' onClick={handleClose}>
<Tooltip content='Close' position='left'>
<Icon icon='mdi:close' width={20} height={20} />
</Tooltip>
</Button>
</DrawerHeader>
{/* Form Section */}
<div className='divider mt-3.5'></div>
<section className='w-full px-6'>
{initialValues ? (
<div className='flex flex-col gap-4'>
{/* Info Umum */}
<div className=''>
<p className='text-sm font-medium mb-5'>Informasi Umum</p>
<Table<DetailOptionType>
data={infoUmumTableData}
columns={columnsInfoUmum}
pageSize={5}
className={{
containerClassName: 'mb-0',
paginationClassName: 'hidden',
}}
/>
</div>
{/* Sampling */}
<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={columnsResult}
pageSize={4}
className={{
containerClassName: 'mb-0',
paginationClassName: 'hidden',
}}
/>
</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 detail not found</p>
</div>
)}
</section>
</section>
);
};
export default UniformityDetail;