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 = () => { const Closing = () => {
return ( return (
<section className='w-full p-4'> <section className='w-full p-3'>
<ClosingsTable /> <ClosingsTable />
</section> </section>
); );
+132 -84
View File
@@ -3,15 +3,15 @@
import { ChangeEventHandler, useEffect, useState, useMemo } from 'react'; import { ChangeEventHandler, useEffect, useState, useMemo } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import { useRouter } from 'next/navigation';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button'; import Button from '@/components/Button';
import SelectInput, { useSelect } from '@/components/input/SelectInput'; import SelectInput, { useSelect } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import PopoverButton from '@/components/popover/PopoverButton';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import PopoverContent from '@/components/popover/PopoverContent';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import Modal, { useModal } from '@/components/Modal'; import Modal, { useModal } from '@/components/Modal';
import SelectInputRadio from '@/components/input/SelectInputRadio'; import SelectInputRadio from '@/components/input/SelectInputRadio';
@@ -31,32 +31,66 @@ import {
import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton'; import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
type = 'dropdown',
props, props,
popoverPosition = 'bottom',
detailClickHandler,
}: { }: {
type: 'dropdown' | 'collapse';
props: CellContext<Closing, unknown>; 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 ( return (
<RowOptionsMenuWrapper type={type}> <div className='relative'>
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'> <PopoverButton
<RequirePermission permissions='lti.closing.detail'> tabIndex={0}
<Button variant='ghost'
href={`/closing/detail/?closingId=${props.row.original.id}`} color='none'
variant='ghost' popoverTarget={popoverId}
color='primary' anchorName={popoverAnchorName}
className='justify-start text-sm' >
> <Icon icon='material-symbols:more-vert' width={16} height={16} />
<Icon icon='mdi:eye-outline' width={16} height={16} /> </PopoverButton>
Detail
</Button> <PopoverContent
</RequirePermission> id={popoverId}
</div> anchorName={popoverAnchorName}
</RowOptionsMenuWrapper> 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 = () => { const ClosingsTable = () => {
// ===== ROUTER =====
const router = useRouter();
// ===== FILTER MODAL STATE ===== // ===== FILTER MODAL STATE =====
const filterModal = useModal(); const filterModal = useModal();
@@ -170,22 +204,18 @@ const ClosingsTable = () => {
const currentRowRelativeIndex = const currentRowRelativeIndex =
currentPageRows.findIndex((r) => r.id === props.row.id) + 1; 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 ( return (
<> <RowOptionsMenu
{currentPageSize > 3 && ( props={props}
<RowDropdownOptions isLast2Rows={isLast2Rows}> detailClickHandler={detailClickHandler}
<RowOptionsMenu type='dropdown' props={props} /> popoverPosition={isLast2Rows ? 'top' : 'bottom'}
</RowDropdownOptions> />
)}
{currentPageSize <= 3 && (
<RowCollapseOptions>
<RowOptionsMenu type='collapse' props={props} />
</RowCollapseOptions>
)}
</>
); );
}, },
}, },
@@ -268,16 +298,32 @@ const ClosingsTable = () => {
return ( return (
<> <>
<div className='w-full p-0'> <div className='w-full'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col mb-4'>
<div className='flex flex-col gap-2 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-full flex flex-col sm:flex-row justify-end items-end sm:items-center gap-4'> <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 <DebouncedTextInput
name='search' name='search'
placeholder='Cari Closing' placeholder='Search'
value={tableFilterState.search} value={tableFilterState.search ?? ''}
onChange={searchChangeHandler} 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 <Button
@@ -301,50 +347,52 @@ const ClosingsTable = () => {
</Button> </Button>
</div> </div>
</div> </div>
</div>
{isLoadingClosings ? ( {isLoadingClosings ? (
<div className='w-full flex flex-row justify-center items-center p-4'> <div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' /> <span className='loading loading-spinner loading-xl' />
</div> </div>
) : data.length === 0 ? ( ) : data.length === 0 ? (
<ClosingTableSkeleton <ClosingTableSkeleton
columns={closingsColumns} columns={closingsColumns}
icon={ icon={
<Icon <Icon
icon='heroicons:chart-bar' icon='heroicons:chart-bar'
className='text-white' className='text-white'
width={20} width={20}
height={20} height={20}
/> />
} }
title='Data Closing Belum Tersedia' title='Data Closing Belum Tersedia'
subtitle='Tidak ada data closing untuk saat ini.' subtitle='Tidak ada data closing untuk saat ini.'
/> />
) : ( ) : (
<Table<Closing> <Table<Closing>
data={data} data={isResponseSuccess(closings) ? closings?.data : []}
columns={closingsColumns} columns={closingsColumns}
pageSize={tableFilterState.pageSize} pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize} onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]} rowOptions={[10, 20, 50, 100]}
page={tableFilterState.page} page={isResponseSuccess(closings) ? closings?.meta?.page : 0}
totalItems={ totalItems={
isResponseSuccess(closings) ? closings?.meta?.total_results : 0 isResponseSuccess(closings) ? closings?.meta?.total_results : 0
} }
onPageChange={setPage} onPageChange={setPage}
isLoading={isLoadingClosings} isLoading={isLoadingClosings}
sorting={sorting} sorting={sorting}
setSorting={setSorting} setSorting={setSorting}
rowSelection={rowSelection} rowSelection={rowSelection}
setRowSelection={setRowSelection} setRowSelection={setRowSelection}
className={{ className={{
containerClassName: cn({ containerClassName: cn('mt-3', {
'w-full mb-0': data.length === 0, 'w-full mb-0':
}), isResponseSuccess(closings) && closings?.data?.length === 0,
}} }),
/> headerColumnClassName: 'text-nowrap',
)} }}
/>
)}
</div>
</div> </div>
{/* Filter Modal */} {/* Filter Modal */}