refactor(FE-316): Refactor Uniformity table and status helpers

This commit is contained in:
rstubryan
2025-12-24 10:51:12 +07:00
parent 5fae7752f2
commit b9c1989cae
3 changed files with 201 additions and 168 deletions
@@ -1,7 +1,7 @@
import React from 'react';
import Card from '@/components/Card';
import UniformityBarChart from './chart/UniformityBarChart';
import UniformityGaugeChart from './chart/UniformityGaugeChart';
import UniformityBarChart from '@/components/pages/uniformity/chart/UniformityBarChart';
import UniformityGaugeChart from '@/components/pages/uniformity/chart/UniformityGaugeChart';
interface BarChartData {
name: string;
@@ -1,6 +1,6 @@
'use client';
import React, { useCallback, useState, useEffect } from 'react';
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import useSWR from 'swr';
import { Icon } from '@iconify/react';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
@@ -22,16 +22,60 @@ import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import toast from 'react-hot-toast';
import Card from '@/components/Card';
import { Color } from '@/types/theme';
const statusColorMap: Record<string, Color> = {
APPROVED: 'success',
REJECTED: 'error',
CREATED: 'none',
};
const statusIndicatorColorMap: Record<string, string> = {
APPROVED: 'bg-success',
REJECTED: 'bg-error',
CREATED: 'bg-[#D9D9D9]',
};
const statusTextMap: Record<string, string> = {
APPROVED: 'Disetujui',
REJECTED: 'Ditolak',
CREATED: 'Pengajuan',
};
const getStatusColor = (status: string): Color => {
return statusColorMap[status] || 'info';
};
const getStatusIndicatorColor = (status: string): string => {
return statusIndicatorColorMap[status] || 'bg-info';
};
const getStatusText = (status: string): string => {
return statusTextMap[status] || status;
};
const isUniformityLocked = (uniformity: Uniformity): boolean => {
return uniformity.status === 'APPROVED' || uniformity.status === 'REJECTED';
};
const RowOptionsMenu = ({
type = 'dropdown',
props,
deleteClickHandler,
setSelectedUniformity,
openModal,
}: {
type: 'dropdown' | 'collapse';
props: CellContext<Uniformity, unknown>;
deleteClickHandler: () => void;
setSelectedUniformity: (uniformity: Uniformity) => void;
openModal: () => void;
}) => {
const handleDeleteClick = useCallback(() => {
setSelectedUniformity(props.row.original);
openModal();
}, [props.row.original, setSelectedUniformity, openModal]);
return (
<RowOptionsMenuWrapper type={type}>
<Button
@@ -53,7 +97,7 @@ const RowOptionsMenu = ({
Edit
</Button>
<Button
onClick={deleteClickHandler}
onClick={handleDeleteClick}
variant='ghost'
color='error'
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
@@ -104,11 +148,7 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
UniformityApi.getAllFetcher
);
const isUniformityLocked = useCallback((uniformity: Uniformity): boolean => {
return uniformity.status === 'APPROVED' || uniformity.status === 'REJECTED';
}, []);
const singleDeleteHandler = async () => {
const singleDeleteHandler = useCallback(async () => {
setIsDeleteLoading(true);
await UniformityApi.delete(selectedUniformity?.id as number);
@@ -117,7 +157,7 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
singleDeleteModal.closeModal();
toast.success('Successfully delete Uniformity!');
setIsDeleteLoading(false);
};
}, [selectedUniformity?.id, refreshUniformities, singleDeleteModal]);
useEffect(() => {
if (isResponseSuccess(uniformities) && uniformities.data) {
@@ -140,176 +180,169 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
setRowSelection(newSelection);
}
}
}, [uniformities, rowSelection, isUniformityLocked, setRowSelection]);
const getStatusColor = (status: string) => {
switch (status) {
case 'APPROVED':
return 'success';
case 'REJECTED':
return 'error';
case 'CREATED':
return 'info';
default:
return 'info';
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'APPROVED':
return 'Disetujui';
case 'REJECTED':
return 'Ditolak';
case 'CREATED':
return 'Pengajuan';
default:
return status;
}
};
}, [uniformities, rowSelection]);
// ===== TABLE COLUMNS DEFINITION =====
const uniformityColumns: ColumnDef<Uniformity>[] = [
{
id: 'select',
header: ({ table }) => {
const allRows = table.getRowModel().rows;
const selectableRows = allRows.filter((row) => {
const uniformity = row.original;
return !isUniformityLocked(uniformity);
});
const uniformityColumns: ColumnDef<Uniformity>[] = useMemo(
() => [
{
id: 'select',
header: ({ table }) => {
const allRows = table.getRowModel().rows;
const selectableRows = allRows.filter((row) => {
const uniformity = row.original;
return !isUniformityLocked(uniformity);
});
const hasNoSelectableRows = selectableRows.length === 0;
const hasNoSelectableRows = selectableRows.length === 0;
const handleSelectAll = () => {
const isAllSelected = selectableRows.every((row) =>
const handleSelectAll = () => {
const isAllSelected = selectableRows.every((row) =>
row.getIsSelected()
);
selectableRows.forEach((row) => {
row.toggleSelected(!isAllSelected);
});
};
const isAllSelected =
selectableRows.length > 0 &&
selectableRows.every((row) => row.getIsSelected());
const isSomeSelected = selectableRows.some((row) =>
row.getIsSelected()
);
selectableRows.forEach((row) => {
row.toggleSelected(!isAllSelected);
});
};
return (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={isAllSelected}
indeterminate={isSomeSelected && !isAllSelected}
onChange={handleSelectAll}
disabled={hasNoSelectableRows}
/>
</div>
);
},
cell: ({ row }) => {
const uniformity = row.original;
const isDisabled = isUniformityLocked(uniformity);
const isAllSelected =
selectableRows.length > 0 &&
selectableRows.every((row) => row.getIsSelected());
const isSomeSelected = selectableRows.some((row) =>
row.getIsSelected()
);
return (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={isAllSelected}
indeterminate={isSomeSelected && !isAllSelected}
onChange={handleSelectAll}
disabled={hasNoSelectableRows}
/>
</div>
);
return (
<div className={cn({ 'opacity-50': isDisabled })}>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
disabled={isDisabled}
/>
</div>
);
},
},
cell: ({ row }) => {
const uniformity = row.original;
const isDisabled = isUniformityLocked(uniformity);
return (
<div className={cn({ 'opacity-50': isDisabled })}>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
disabled={isDisabled}
/>
</div>
);
{
accessorKey: 'location.name',
header: 'Lokasi',
cell: (props) => props.row.original.location.name || '-',
},
},
{
accessorKey: 'location.name',
header: 'Lokasi',
cell: (props) => props.row.original.location.name || '-',
},
{
accessorKey: 'project_flock_kandang_id',
header: 'Flock',
cell: (props) => `Flock ${props.row.original.project_flock_kandang_id}`,
},
{
accessorKey: 'kandang.name',
header: 'Kandang',
cell: (props) => props.row.original.kandang.name || '-',
},
{
accessorKey: 'week',
header: 'Tanggal (Week)',
cell: (props) => `Week ${props.row.original.week}`,
},
{
accessorKey: 'status',
header: 'Status',
cell: (props) => {
const status = props.row.original.status;
return (
<Badge variant='soft' color={getStatusColor(status)}>
{getStatusText(status)}
</Badge>
);
{
accessorKey: 'project_flock_kandang_id',
header: 'Flock',
cell: (props) => `Flock ${props.row.original.project_flock_kandang_id}`,
},
},
{
accessorKey: 'uniformity',
header: 'Uniformity',
cell: (props) => {
const uniformity = props.row.original.uniformity;
return <span>{uniformity}%</span>;
{
accessorKey: 'kandang.name',
header: 'Kandang',
cell: (props) => props.row.original.kandang.name || '-',
},
},
{
id: 'actions',
header: 'Aksi',
cell: (props: CellContext<Uniformity, unknown>) => {
const currentPageSize = props.table.getPaginationRowModel().rows.length;
const currentPageRows = props.table.getPaginationRowModel().flatRows;
const currentRowRelativeIndex =
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
const deleteClickHandler = () => {
setSelectedUniformity(props.row.original);
singleDeleteModal.openModal();
};
return (
<>
{currentPageSize > 2 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu
type='dropdown'
props={props}
deleteClickHandler={deleteClickHandler}
/>
</RowDropdownOptions>
)}
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
</RowCollapseOptions>
)}
</>
);
{
accessorKey: 'week',
header: 'Tanggal (Week)',
cell: (props) => `Week ${props.row.original.week}`,
},
},
];
{
accessorKey: 'status',
header: 'Status',
cell: (props) => {
const status = props.row.original.status;
return (
<div className='w-full'>
<Badge
statusIndicator={true}
variant='soft'
color={getStatusColor(status)}
className={{
badge: `rounded-xl w-full justify-start border border-gray-200`,
status: getStatusIndicatorColor(status),
}}
>
{getStatusText(status)}
</Badge>
</div>
);
},
},
{
accessorKey: 'uniformity',
header: 'Uniformity',
cell: (props) => {
const uniformity = props.row.original.uniformity;
return <span>{uniformity}%</span>;
},
},
{
id: 'actions',
header: 'Aksi',
cell: (props: CellContext<Uniformity, unknown>) => {
const currentPageSize =
props.table.getPaginationRowModel().rows.length;
const currentPageRows = props.table.getPaginationRowModel().flatRows;
const currentRowRelativeIndex =
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
return (
<>
{currentPageSize > 2 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu
type='dropdown'
props={props}
deleteClickHandler={() => {
setSelectedUniformity(props.row.original);
singleDeleteModal.openModal();
}}
setSelectedUniformity={setSelectedUniformity}
openModal={singleDeleteModal.openModal}
/>
</RowDropdownOptions>
)}
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='collapse'
props={props}
deleteClickHandler={() => {
setSelectedUniformity(props.row.original);
singleDeleteModal.openModal();
}}
setSelectedUniformity={setSelectedUniformity}
openModal={singleDeleteModal.openModal}
/>
</RowCollapseOptions>
)}
</>
);
},
},
],
[]
);
return (
<>
+1 -1
View File
@@ -1,6 +1,6 @@
import { Location } from '@/types/api/location/location';
import { Kandang } from '@/types/api/kandang/kandang';
import { BaseMetadata } from '../api-general';
import { BaseMetadata } from '@/types/common/base-metadata';
export type Uniformity = BaseMetadata & {
id: number;