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 React from 'react';
import Card from '@/components/Card'; import Card from '@/components/Card';
import UniformityBarChart from './chart/UniformityBarChart'; import UniformityBarChart from '@/components/pages/uniformity/chart/UniformityBarChart';
import UniformityGaugeChart from './chart/UniformityGaugeChart'; import UniformityGaugeChart from '@/components/pages/uniformity/chart/UniformityGaugeChart';
interface BarChartData { interface BarChartData {
name: string; name: string;
@@ -1,6 +1,6 @@
'use client'; 'use client';
import React, { useCallback, useState, useEffect } from 'react'; import React, { useCallback, useState, useEffect, useMemo } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
@@ -22,16 +22,60 @@ import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Card from '@/components/Card'; 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 = ({ const RowOptionsMenu = ({
type = 'dropdown', type = 'dropdown',
props, props,
deleteClickHandler, deleteClickHandler,
setSelectedUniformity,
openModal,
}: { }: {
type: 'dropdown' | 'collapse'; type: 'dropdown' | 'collapse';
props: CellContext<Uniformity, unknown>; props: CellContext<Uniformity, unknown>;
deleteClickHandler: () => void; deleteClickHandler: () => void;
setSelectedUniformity: (uniformity: Uniformity) => void;
openModal: () => void;
}) => { }) => {
const handleDeleteClick = useCallback(() => {
setSelectedUniformity(props.row.original);
openModal();
}, [props.row.original, setSelectedUniformity, openModal]);
return ( return (
<RowOptionsMenuWrapper type={type}> <RowOptionsMenuWrapper type={type}>
<Button <Button
@@ -53,7 +97,7 @@ const RowOptionsMenu = ({
Edit Edit
</Button> </Button>
<Button <Button
onClick={deleteClickHandler} onClick={handleDeleteClick}
variant='ghost' variant='ghost'
color='error' color='error'
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content' 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 UniformityApi.getAllFetcher
); );
const isUniformityLocked = useCallback((uniformity: Uniformity): boolean => { const singleDeleteHandler = useCallback(async () => {
return uniformity.status === 'APPROVED' || uniformity.status === 'REJECTED';
}, []);
const singleDeleteHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
await UniformityApi.delete(selectedUniformity?.id as number); await UniformityApi.delete(selectedUniformity?.id as number);
@@ -117,7 +157,7 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
singleDeleteModal.closeModal(); singleDeleteModal.closeModal();
toast.success('Successfully delete Uniformity!'); toast.success('Successfully delete Uniformity!');
setIsDeleteLoading(false); setIsDeleteLoading(false);
}; }, [selectedUniformity?.id, refreshUniformities, singleDeleteModal]);
useEffect(() => { useEffect(() => {
if (isResponseSuccess(uniformities) && uniformities.data) { if (isResponseSuccess(uniformities) && uniformities.data) {
@@ -140,176 +180,169 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
setRowSelection(newSelection); setRowSelection(newSelection);
} }
} }
}, [uniformities, rowSelection, isUniformityLocked, setRowSelection]); }, [uniformities, rowSelection]);
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;
}
};
// ===== TABLE COLUMNS DEFINITION ===== // ===== TABLE COLUMNS DEFINITION =====
const uniformityColumns: ColumnDef<Uniformity>[] = [ const uniformityColumns: ColumnDef<Uniformity>[] = useMemo(
{ () => [
id: 'select', {
header: ({ table }) => { id: 'select',
const allRows = table.getRowModel().rows; header: ({ table }) => {
const selectableRows = allRows.filter((row) => { const allRows = table.getRowModel().rows;
const uniformity = row.original; const selectableRows = allRows.filter((row) => {
return !isUniformityLocked(uniformity); const uniformity = row.original;
}); return !isUniformityLocked(uniformity);
});
const hasNoSelectableRows = selectableRows.length === 0; const hasNoSelectableRows = selectableRows.length === 0;
const handleSelectAll = () => { const handleSelectAll = () => {
const isAllSelected = selectableRows.every((row) => 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() row.getIsSelected()
); );
selectableRows.forEach((row) => { return (
row.toggleSelected(!isAllSelected); <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 = return (
selectableRows.length > 0 && <div className={cn({ 'opacity-50': isDisabled })}>
selectableRows.every((row) => row.getIsSelected()); <CheckboxInput
name='row'
const isSomeSelected = selectableRows.some((row) => checked={row.getIsSelected()}
row.getIsSelected() indeterminate={row.getIsSomeSelected()}
); onChange={row.getToggleSelectedHandler()}
disabled={isDisabled}
return ( />
<div className='w-full flex flex-row justify-center'> </div>
<CheckboxInput );
name='allRow' },
checked={isAllSelected}
indeterminate={isSomeSelected && !isAllSelected}
onChange={handleSelectAll}
disabled={hasNoSelectableRows}
/>
</div>
);
}, },
cell: ({ row }) => { {
const uniformity = row.original; accessorKey: 'location.name',
const isDisabled = isUniformityLocked(uniformity); header: 'Lokasi',
cell: (props) => props.row.original.location.name || '-',
return (
<div className={cn({ 'opacity-50': isDisabled })}>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
disabled={isDisabled}
/>
</div>
);
}, },
}, {
{ accessorKey: 'project_flock_kandang_id',
accessorKey: 'location.name', header: 'Flock',
header: 'Lokasi', cell: (props) => `Flock ${props.row.original.project_flock_kandang_id}`,
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: 'kandang.name',
accessorKey: 'uniformity', header: 'Kandang',
header: 'Uniformity', cell: (props) => props.row.original.kandang.name || '-',
cell: (props) => {
const uniformity = props.row.original.uniformity;
return <span>{uniformity}%</span>;
}, },
}, {
{ accessorKey: 'week',
id: 'actions', header: 'Tanggal (Week)',
header: 'Aksi', cell: (props) => `Week ${props.row.original.week}`,
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: '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 ( return (
<> <>
+1 -1
View File
@@ -1,6 +1,6 @@
import { Location } from '@/types/api/location/location'; import { Location } from '@/types/api/location/location';
import { Kandang } from '@/types/api/kandang/kandang'; import { Kandang } from '@/types/api/kandang/kandang';
import { BaseMetadata } from '../api-general'; import { BaseMetadata } from '@/types/common/base-metadata';
export type Uniformity = BaseMetadata & { export type Uniformity = BaseMetadata & {
id: number; id: number;