refactor(FE): Refactor ClosingsTable component and update styles

This commit is contained in:
rstubryan
2026-02-19 14:33:35 +07:00
parent 4f018eb2b1
commit e9784bd5ed
2 changed files with 133 additions and 85 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ import ClosingsTable from '@/components/pages/closing/ClosingsTable';
const Closing = () => {
return (
<section className='w-full p-4'>
<section className='w-full p-3'>
<ClosingsTable />
</section>
);
+132 -84
View File
@@ -3,15 +3,15 @@
import { ChangeEventHandler, useEffect, useState, useMemo } from 'react';
import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import { useRouter } from 'next/navigation';
import { Icon } from '@iconify/react';
import Table from '@/components/Table';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import SelectInput, { useSelect } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import PopoverButton from '@/components/popover/PopoverButton';
import PopoverContent from '@/components/popover/PopoverContent';
import RequirePermission from '@/components/helper/RequirePermission';
import Modal, { useModal } from '@/components/Modal';
import SelectInputRadio from '@/components/input/SelectInputRadio';
@@ -31,32 +31,66 @@ import {
import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton';
const RowOptionsMenu = ({
type = 'dropdown',
props,
popoverPosition = 'bottom',
detailClickHandler,
}: {
type: 'dropdown' | 'collapse';
props: CellContext<Closing, unknown>;
popoverPosition: 'bottom' | 'top';
detailClickHandler: (id: number) => void;
}) => {
const popoverId = `closing#${props.row.original.id}`;
const popoverAnchorName = `--anchor-closing#${props.row.original.id}`;
const closePopover = () => {
document.getElementById(popoverId)?.hidePopover();
};
const detailClickHandlerWrapper = () => {
detailClickHandler(props.row.original.id);
closePopover();
};
return (
<RowOptionsMenuWrapper type={type}>
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'>
<RequirePermission permissions='lti.closing.detail'>
<Button
href={`/closing/detail/?closingId=${props.row.original.id}`}
variant='ghost'
color='primary'
className='justify-start text-sm'
>
<Icon icon='mdi:eye-outline' width={16} height={16} />
Detail
</Button>
</RequirePermission>
</div>
</RowOptionsMenuWrapper>
<div className='relative'>
<PopoverButton
tabIndex={0}
variant='ghost'
color='none'
popoverTarget={popoverId}
anchorName={popoverAnchorName}
>
<Icon icon='material-symbols:more-vert' width={16} height={16} />
</PopoverButton>
<PopoverContent
id={popoverId}
anchorName={popoverAnchorName}
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
>
<div className='flex flex-col bg-base-100 rounded-xl'>
<RequirePermission permissions='lti.closing.detail'>
<Button
variant='ghost'
color='none'
onClick={detailClickHandlerWrapper}
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:eye' width={20} height={20} />
View Details
</Button>
</RequirePermission>
</div>
</PopoverContent>
</div>
);
};
const ClosingsTable = () => {
// ===== ROUTER =====
const router = useRouter();
// ===== FILTER MODAL STATE =====
const filterModal = useModal();
@@ -170,22 +204,18 @@ const ClosingsTable = () => {
const currentRowRelativeIndex =
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 3;
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
const detailClickHandler = (id: number) => {
router.push(`/closing/detail/?closingId=${id}`);
};
return (
<>
{currentPageSize > 3 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu type='dropdown' props={props} />
</RowDropdownOptions>
)}
{currentPageSize <= 3 && (
<RowCollapseOptions>
<RowOptionsMenu type='collapse' props={props} />
</RowCollapseOptions>
)}
</>
<RowOptionsMenu
props={props}
detailClickHandler={detailClickHandler}
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
/>
);
},
},
@@ -268,16 +298,32 @@ const ClosingsTable = () => {
return (
<>
<div className='w-full p-0'>
<div className='flex flex-col gap-2 mb-4'>
<div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-end items-end sm:items-center gap-4'>
<div className='w-full'>
<div className='flex flex-col mb-4'>
<div className='w-full p-3 pt-0 px-0 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
<div className='w-fit flex flex-row gap-3 flex-wrap'>
{/* Space for action buttons if needed in the future */}
</div>
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
<DebouncedTextInput
name='search'
placeholder='Cari Closing'
value={tableFilterState.search}
placeholder='Search'
value={tableFilterState.search ?? ''}
onChange={searchChangeHandler}
className={{ wrapper: 'sm:max-w-3xs' }}
startAdornment={
<Icon
icon='heroicons:magnifying-glass'
width={20}
height={20}
/>
}
className={{
wrapper: 'w-full min-w-24 max-w-3xs',
inputWrapper: 'rounded-xl! shadow-button-soft',
input:
'placeholder:font-semibold placeholder:text-base-content/50',
}}
/>
<Button
@@ -301,50 +347,52 @@ const ClosingsTable = () => {
</Button>
</div>
</div>
</div>
{isLoadingClosings ? (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
) : data.length === 0 ? (
<ClosingTableSkeleton
columns={closingsColumns}
icon={
<Icon
icon='heroicons:chart-bar'
className='text-white'
width={20}
height={20}
/>
}
title='Data Closing Belum Tersedia'
subtitle='Tidak ada data closing untuk saat ini.'
/>
) : (
<Table<Closing>
data={data}
columns={closingsColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={tableFilterState.page}
totalItems={
isResponseSuccess(closings) ? closings?.meta?.total_results : 0
}
onPageChange={setPage}
isLoading={isLoadingClosings}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn({
'w-full mb-0': data.length === 0,
}),
}}
/>
)}
{isLoadingClosings ? (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
) : data.length === 0 ? (
<ClosingTableSkeleton
columns={closingsColumns}
icon={
<Icon
icon='heroicons:chart-bar'
className='text-white'
width={20}
height={20}
/>
}
title='Data Closing Belum Tersedia'
subtitle='Tidak ada data closing untuk saat ini.'
/>
) : (
<Table<Closing>
data={isResponseSuccess(closings) ? closings?.data : []}
columns={closingsColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={isResponseSuccess(closings) ? closings?.meta?.page : 0}
totalItems={
isResponseSuccess(closings) ? closings?.meta?.total_results : 0
}
onPageChange={setPage}
isLoading={isLoadingClosings}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn('mt-3', {
'w-full mb-0':
isResponseSuccess(closings) && closings?.data?.length === 0,
}),
headerColumnClassName: 'text-nowrap',
}}
/>
)}
</div>
</div>
{/* Filter Modal */}