refactor(FE): Refactor table components to improve styling and structure

This commit is contained in:
rstubryan
2026-02-19 10:43:37 +07:00
parent 944db8dba7
commit 4c70ec7cab
9 changed files with 539 additions and 509 deletions
@@ -82,7 +82,7 @@ const FinanceClosingTable = ({
}, [finance]); }, [finance]);
return ( return (
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4 p-0 sm:p-3'>
{isLoading ? ( {isLoading ? (
<FinanceClosingSkeleton /> <FinanceClosingSkeleton />
) : !isResponseSuccess(finance) ? ( ) : !isResponseSuccess(finance) ? (
@@ -96,10 +96,13 @@ const FinanceClosingTable = ({
<Card <Card
variant='bordered' variant='bordered'
className={{ className={{
wrapper: 'w-full', wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}} }}
> >
<div className='grid grid-cols-2 gap-6'> <div className='p-4 grid grid-cols-2 gap-6'>
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
<div>Laba Rugi Brutto</div> <div>Laba Rugi Brutto</div>
<div className='text-lg font-bold'> <div className='text-lg font-bold'>
@@ -127,10 +130,13 @@ const FinanceClosingTable = ({
variant='bordered' variant='bordered'
collapsible collapsible
className={{ className={{
wrapper: 'w-full', wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}} }}
> >
<div className='mt-6 p-0 mb-0'> <div className='p-0'>
<Table<HppItem> <Table<HppItem>
data={hppTableData} data={hppTableData}
isLoading={isLoading} isLoading={isLoading}
@@ -263,6 +269,24 @@ const FinanceClosingTable = ({
], ],
}, },
]} ]}
className={{
containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}}
renderCustomRow={(row) => { renderCustomRow={(row) => {
const rowData = row.original; const rowData = row.original;
if (rowData.code === 'custom_row') { if (rowData.code === 'custom_row') {
@@ -296,10 +320,13 @@ const FinanceClosingTable = ({
variant='bordered' variant='bordered'
collapsible collapsible
className={{ className={{
wrapper: 'w-full', wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}} }}
> >
<div className='mt-6 p-0 mb-0'> <div className='p-0'>
<Table<ProfitLossItem> <Table<ProfitLossItem>
data={profitLossTableData} data={profitLossTableData}
isLoading={isLoading} isLoading={isLoading}
@@ -363,6 +390,25 @@ const FinanceClosingTable = ({
), ),
}, },
]} ]}
className={{
containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
paginationClassName: 'hidden',
}}
renderCustomRow={(row) => { renderCustomRow={(row) => {
const rowData = row.original; const rowData = row.original;
if (rowData.code === 'custom_row') { if (rowData.code === 'custom_row') {
@@ -404,9 +450,6 @@ const FinanceClosingTable = ({
} }
return null; return null;
}} }}
className={{
paginationClassName: 'hidden',
}}
renderFooter={isResponseSuccess(finance)} renderFooter={isResponseSuccess(finance)}
/> />
</div> </div>
@@ -91,53 +91,56 @@ const HppExpeditionClosingTable = ({
); );
return ( return (
<> <div className='w-full p-0 sm:p-3'>
<section className='w-full'> <Card
<div className='p-4'> className={{
<h2 className='text-xl font-semibold mb-4'>HPP Ekspedisi</h2> wrapper: 'w-full rounded-lg border-none',
<Card body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}}
variant='bordered'
title='HPP Ekspedisi'
collapsible
defaultCollapsed={false}
>
{isLoading ? (
<HppExpeditionClosingSkeleton
columns={costOfRevenueExpeditionColumns}
/>
) : costOfRevenueExpeditionData.length === 0 ? (
<HppExpeditionClosingSkeleton
columns={costOfRevenueExpeditionColumns}
iconName='heroicons:chart-bar'
/>
) : (
<Table
data={costOfRevenueExpeditionData}
columns={costOfRevenueExpeditionColumns}
isLoading={isLoading}
renderFooter={costOfRevenueExpeditionData.length > 0}
className={{ className={{
wrapper: 'w-full bg-base-100', containerClassName: 'w-full mb-0!',
body: 'p-0', tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
> />
{isLoading ? ( )}
<HppExpeditionClosingSkeleton </Card>
columns={costOfRevenueExpeditionColumns} </div>
/>
) : costOfRevenueExpeditionData.length === 0 ? (
<HppExpeditionClosingSkeleton
columns={costOfRevenueExpeditionColumns}
iconName='heroicons:chart-bar'
/>
) : (
<Table
data={costOfRevenueExpeditionData}
columns={costOfRevenueExpeditionColumns}
isLoading={isLoading}
renderFooter={costOfRevenueExpeditionData.length > 0}
className={{
tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}}
/>
)}
</Card>
</div>
</section>
</>
); );
}; };
@@ -209,15 +209,18 @@ const OverheadClosingTable = ({
); );
return ( return (
<> <div className='w-full p-0 sm:p-3'>
<Card <Card
className={{
wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}}
variant='bordered'
title='Pengeluaran Overhead' title='Pengeluaran Overhead'
collapsible collapsible
defaultCollapsed={false} defaultCollapsed={false}
className={{
wrapper: 'w-full',
body: 'p-4 shadow',
}}
> >
{isLoadingOverhead ? ( {isLoadingOverhead ? (
<OverheadClosingSkeleton columns={columns} /> <OverheadClosingSkeleton columns={columns} />
@@ -243,11 +246,24 @@ const OverheadClosingTable = ({
} }
columns={columns} columns={columns}
className={{ className={{
containerClassName: 'my-4', containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName: cn( headerColumnClassName: cn(
TABLE_DEFAULT_STYLING.headerColumnClassName, 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
'whitespace-nowrap' 'whitespace-nowrap'
), ),
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
isLoading={isLoadingOverhead} isLoading={isLoadingOverhead}
renderFooter={ renderFooter={
@@ -312,7 +328,7 @@ const OverheadClosingTable = ({
</Card> </Card>
)} )}
</Card> </Card>
</> </div>
); );
}; };
@@ -316,50 +316,54 @@ const SalesClosingTable = ({ projectFlockId }: SalesClosingTableProps) => {
); );
return ( return (
<> <div className='w-full p-0 sm:p-3'>
<section className='w-full'> <Card
<div className='p-4'> className={{
<h2 className='text-xl font-semibold mb-4'>Penjualan</h2> wrapper: 'w-full rounded-lg border-none',
<Card body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}}
variant='bordered'
title='Penjualan'
collapsible
defaultCollapsed={false}
>
{isLoading ? (
<SalesClosingSkeleton columns={salesColumns} />
) : salesData.length === 0 ? (
<SalesClosingSkeleton
columns={salesColumns}
iconName='heroicons:chart-bar'
/>
) : (
<Table
data={salesData}
columns={salesColumns}
isLoading={isLoading}
renderFooter={salesData.length > 0}
className={{ className={{
wrapper: 'w-full bg-base-100', containerClassName: 'w-full mb-0!',
body: 'p-0', tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
> />
{isLoading ? ( )}
<SalesClosingSkeleton columns={salesColumns} /> </Card>
) : salesData.length === 0 ? ( </div>
<SalesClosingSkeleton
columns={salesColumns}
iconName='heroicons:chart-bar'
/>
) : (
<Table
data={salesData}
columns={salesColumns}
isLoading={isLoading}
renderFooter={salesData.length > 0}
className={{
tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'w-full table-auto text-sm',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}}
/>
)}
</Card>
</div>
</section>
</>
); );
}; };
@@ -179,7 +179,7 @@ const SapronakCalculationClosingTable = ({
); );
return ( return (
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4 p-0 sm:p-3'>
{/* Table DOC jika kategori Project Flock Growing */} {/* Table DOC jika kategori Project Flock Growing */}
<Card <Card
title={ title={
@@ -190,9 +190,12 @@ const SapronakCalculationClosingTable = ({
collapsible collapsible
defaultCollapsed={false} defaultCollapsed={false}
className={{ className={{
wrapper: 'w-full', wrapper: 'w-full rounded-lg border-none',
body: 'p-4 shadow', body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}} }}
variant='bordered'
> >
{isLoading ? ( {isLoading ? (
<SapronakCalculationClosingSkeleton columns={docColumns} /> <SapronakCalculationClosingSkeleton columns={docColumns} />
@@ -213,7 +216,22 @@ const SapronakCalculationClosingTable = ({
} }
columns={docColumns} columns={docColumns}
className={{ className={{
containerClassName: 'my-4', containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
renderFooter={ renderFooter={
isResponseSuccess(sapronakCalculation) && isResponseSuccess(sapronakCalculation) &&
@@ -229,7 +247,10 @@ const SapronakCalculationClosingTable = ({
collapsible collapsible
defaultCollapsed={true} defaultCollapsed={true}
className={{ className={{
wrapper: 'w-full', wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}} }}
> >
{isLoading ? ( {isLoading ? (
@@ -251,7 +272,22 @@ const SapronakCalculationClosingTable = ({
} }
columns={ovkColumns} columns={ovkColumns}
className={{ className={{
containerClassName: 'my-4', containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
renderFooter={ renderFooter={
isResponseSuccess(sapronakCalculation) && isResponseSuccess(sapronakCalculation) &&
@@ -267,7 +303,10 @@ const SapronakCalculationClosingTable = ({
collapsible collapsible
defaultCollapsed={true} defaultCollapsed={true}
className={{ className={{
wrapper: 'w-full', wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}} }}
> >
{isLoading ? ( {isLoading ? (
@@ -289,7 +328,22 @@ const SapronakCalculationClosingTable = ({
} }
columns={pakanColumns} columns={pakanColumns}
className={{ className={{
containerClassName: 'my-4', containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
renderFooter={ renderFooter={
isResponseSuccess(sapronakCalculation) && isResponseSuccess(sapronakCalculation) &&
@@ -5,12 +5,10 @@ import { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { ColumnDef, SortingState } from '@tanstack/react-table'; import { ColumnDef, SortingState } from '@tanstack/react-table';
import { Icon } from '@iconify/react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Card from '@/components/Card'; import Card from '@/components/Card';
import Collapse from '@/components/Collapse';
import { cn, formatNumber } from '@/lib/helper'; import { formatNumber } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
@@ -56,8 +54,6 @@ const ClosingIncomingSapronaksSummaryTable = ({
} }
); );
const [open, setOpen] = useState(true);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({}); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
@@ -94,97 +90,78 @@ const ClosingIncomingSapronaksSummaryTable = ({
} }
}, [sorting, updateFilter]); }, [sorting, updateFilter]);
useEffect(() => {
if (!open) {
setOpen(
isResponseSuccess(incomingSapronakSummaries)
? incomingSapronakSummaries.data.length > 0
: false
);
}
}, [incomingSapronakSummaries, isResponseSuccess]);
return ( return (
<Card <div className='w-full p-0 sm:p-3'>
className={{ <Card
wrapper: 'w-full', className={{
body: 'p-4 shadow', wrapper: 'w-full rounded-lg border-none',
}} body: 'p-0',
> title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
<Collapse collapsible: 'rounded-lg',
open={open} }}
onOpenChange={setOpen} variant='bordered'
title={ title='Ringkasan Sapronak Masuk'
<div className='card-actions p-4 justify-between items-center w-full'> collapsible
<div className='card-title'>Ringkasan Sapronak Masuk</div> defaultCollapsed={false}
<Icon
icon='material-symbols:keyboard-arrow-down'
width={24}
height={24}
className={cn('text-primary transition-transform', {
'-rotate-180': open,
})}
/>
</div>
}
className='w-full!'
titleClassName='w-full p-0!'
> >
<div className='w-full p-0'> {isLoadingIncomingSapronakSummaries ? (
{isLoadingIncomingSapronakSummaries ? ( <SapronakClosingSkeleton
<SapronakClosingSkeleton type='incoming'
type='incoming' columns={incomingSapronaksColumns}
columns={incomingSapronaksColumns} />
/> ) : isResponseSuccess(incomingSapronakSummaries) &&
) : isResponseSuccess(incomingSapronakSummaries) && incomingSapronakSummaries.data.length === 0 ? (
incomingSapronakSummaries.data.length === 0 ? ( <SapronakClosingSkeleton
<SapronakClosingSkeleton type='incoming'
type='incoming' columns={incomingSapronaksColumns}
columns={incomingSapronaksColumns} iconName='heroicons:chart-bar'
iconName='heroicons:chart-bar' title='Ringkasan Sapronak Masuk Tidak Ditemukan'
title='Ringkasan Sapronak Masuk Tidak Ditemukan' subtitle='Tidak ada ringkasan sapronak masuk untuk periode ini.'
subtitle='Tidak ada ringkasan sapronak masuk untuk periode ini.' />
/> ) : (
) : ( <Table<ClosingIncomingSapronakSummary>
<Table<ClosingIncomingSapronakSummary> data={
data={ isResponseSuccess(incomingSapronakSummaries)
isResponseSuccess(incomingSapronakSummaries) ? incomingSapronakSummaries?.data
? incomingSapronakSummaries?.data : []
: [] }
} columns={incomingSapronaksColumns}
columns={incomingSapronaksColumns} pageSize={tableFilterState.pageSize}
pageSize={tableFilterState.pageSize} onPageSizeChange={setPageSize}
onPageSizeChange={setPageSize} rowOptions={[10, 20, 50, 100]}
rowOptions={[10, 20, 50, 100]} page={
page={ isResponseSuccess(incomingSapronakSummaries)
isResponseSuccess(incomingSapronakSummaries) ? incomingSapronakSummaries?.meta?.page
? incomingSapronakSummaries?.meta?.page : 0
: 0 }
} totalItems={
totalItems={ isResponseSuccess(incomingSapronakSummaries)
isResponseSuccess(incomingSapronakSummaries) ? incomingSapronakSummaries?.meta?.total_results
? incomingSapronakSummaries?.meta?.total_results : 0
: 0 }
} onPageChange={setPage}
onPageChange={setPage} isLoading={isLoadingIncomingSapronakSummaries}
isLoading={isLoadingIncomingSapronakSummaries} sorting={sorting}
sorting={sorting} setSorting={setSorting}
setSorting={setSorting} rowSelection={rowSelection}
rowSelection={rowSelection} setRowSelection={setRowSelection}
setRowSelection={setRowSelection} className={{
className={{ containerClassName: 'w-full mb-0!',
containerClassName: cn({ tableWrapperClassName:
'w-full mb-20': 'overflow-x-auto rounded-tr-none rounded-tl-none',
isResponseSuccess(incomingSapronakSummaries) && tableClassName: 'w-full table-auto text-sm',
incomingSapronakSummaries?.data?.length === 0, headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
}), headerColumnClassName:
}} 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
/> bodyRowClassName:
)} 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
</div> bodyColumnClassName:
</Collapse> 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
</Card> }}
/>
)}
</Card>
</div>
); );
}; };
@@ -5,13 +5,11 @@ import { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { ColumnDef, SortingState } from '@tanstack/react-table'; import { ColumnDef, SortingState } from '@tanstack/react-table';
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 Card from '@/components/Card'; import Card from '@/components/Card';
import Collapse from '@/components/Collapse';
import { cn, formatDate, formatNumber } from '@/lib/helper'; import { formatDate, formatNumber } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
@@ -52,8 +50,6 @@ const ClosingIncomingSapronaksTable = ({
ClosingApi.getAllIncomingSapronakFetcher ClosingApi.getAllIncomingSapronakFetcher
); );
const [open, setOpen] = useState(true);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({}); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
@@ -118,109 +114,90 @@ const ClosingIncomingSapronaksTable = ({
} }
}, [sorting, updateFilter]); }, [sorting, updateFilter]);
useEffect(() => {
if (!open) {
setOpen(
isResponseSuccess(incomingSapronaks)
? incomingSapronaks.data.length > 0
: false
);
}
}, [incomingSapronaks, isResponseSuccess]);
return ( return (
<Card <div className='w-full p-0 sm:p-3'>
className={{ <Card
wrapper: 'w-full', className={{
body: 'p-4 shadow', wrapper: 'w-full rounded-lg border-none',
}} body: 'p-0',
> title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
<Collapse collapsible: 'rounded-lg',
open={open} }}
onOpenChange={setOpen} variant='bordered'
title={ title='Sapronak Masuk'
<div className='card-actions p-4 justify-between items-center w-full'> collapsible
<div className='card-title'>Sapronak Masuk</div> defaultCollapsed={false}
<Icon
icon='material-symbols:keyboard-arrow-down'
width={24}
height={24}
className={cn('text-primary transition-transform', {
'-rotate-180': open,
})}
/>
</div>
}
className='w-full!'
titleClassName='w-full p-0!'
> >
<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-start items-end sm:items-center gap-4'>
<div className='w-full flex flex-col sm:flex-row justify-start items-end sm:items-center gap-4'> <DebouncedTextInput
<DebouncedTextInput name='search'
name='search' placeholder='Cari Sapronak Masuk'
placeholder='Cari Sapronak Masuk' value={tableFilterState.search}
value={tableFilterState.search} onChange={searchChangeHandler}
onChange={searchChangeHandler} className={{ wrapper: 'sm:max-w-3xs' }}
className={{ wrapper: 'sm:max-w-3xs' }} />
/>
</div>
</div> </div>
{isLoadingIncomingSapronaks ? (
<SapronakClosingSkeleton
type='incoming'
columns={incomingSapronaksColumns}
/>
) : isResponseSuccess(incomingSapronaks) &&
incomingSapronaks.data.length === 0 ? (
<SapronakClosingSkeleton
type='incoming'
columns={incomingSapronaksColumns}
iconName='heroicons:chart-bar'
title='Data Sapronak Masuk Tidak Ditemukan'
subtitle='Tidak ada data sapronak masuk untuk periode ini.'
/>
) : (
<Table<ClosingIncomingSapronak>
data={
isResponseSuccess(incomingSapronaks)
? incomingSapronaks?.data
: []
}
columns={incomingSapronaksColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={
isResponseSuccess(incomingSapronaks)
? incomingSapronaks?.meta?.page
: 0
}
totalItems={
isResponseSuccess(incomingSapronaks)
? incomingSapronaks?.meta?.total_results
: 0
}
onPageChange={setPage}
isLoading={isLoadingIncomingSapronaks}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(incomingSapronaks) &&
incomingSapronaks?.data?.length === 0,
}),
}}
/>
)}
</div> </div>
</Collapse>
</Card> {isLoadingIncomingSapronaks ? (
<SapronakClosingSkeleton
type='incoming'
columns={incomingSapronaksColumns}
/>
) : isResponseSuccess(incomingSapronaks) &&
incomingSapronaks.data.length === 0 ? (
<SapronakClosingSkeleton
type='incoming'
columns={incomingSapronaksColumns}
iconName='heroicons:chart-bar'
title='Data Sapronak Masuk Tidak Ditemukan'
subtitle='Tidak ada data sapronak masuk untuk periode ini.'
/>
) : (
<Table<ClosingIncomingSapronak>
data={
isResponseSuccess(incomingSapronaks)
? incomingSapronaks?.data
: []
}
columns={incomingSapronaksColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={
isResponseSuccess(incomingSapronaks)
? incomingSapronaks?.meta?.page
: 0
}
totalItems={
isResponseSuccess(incomingSapronaks)
? incomingSapronaks?.meta?.total_results
: 0
}
onPageChange={setPage}
isLoading={isLoadingIncomingSapronaks}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}}
/>
)}
</Card>
</div>
); );
}; };
@@ -5,12 +5,10 @@ import { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { ColumnDef, SortingState } from '@tanstack/react-table'; import { ColumnDef, SortingState } from '@tanstack/react-table';
import { Icon } from '@iconify/react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Card from '@/components/Card'; import Card from '@/components/Card';
import Collapse from '@/components/Collapse';
import { cn, formatNumber } from '@/lib/helper'; import { formatNumber } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
@@ -56,8 +54,6 @@ const ClosingOutgoingSapronaksSummaryTable = ({
} }
); );
const [open, setOpen] = useState(true);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({}); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
@@ -94,97 +90,79 @@ const ClosingOutgoingSapronaksSummaryTable = ({
} }
}, [sorting, updateFilter]); }, [sorting, updateFilter]);
useEffect(() => {
if (!open) {
setOpen(
isResponseSuccess(outgoingSapronakSummaries)
? outgoingSapronakSummaries.data.length > 0
: false
);
}
}, [outgoingSapronakSummaries, isResponseSuccess]);
return ( return (
<Card <div className='w-full p-0 sm:p-3'>
className={{ <Card
wrapper: 'w-full', className={{
body: 'p-4 shadow', wrapper: 'w-full rounded-lg border-none',
}} body: 'p-0',
> title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
<Collapse collapsible: 'rounded-lg',
open={open} }}
onOpenChange={setOpen} variant='bordered'
title={ title='Ringkasan Sapronak Keluar'
<div className='card-actions p-4 justify-between items-center w-full'> collapsible
<div className='card-title'>Ringkasan Sapronak Keluar</div> defaultCollapsed={false}
<Icon
icon='material-symbols:keyboard-arrow-down'
width={24}
height={24}
className={cn('text-primary transition-transform', {
'-rotate-180': open,
})}
/>
</div>
}
className='w-full!'
titleClassName='w-full p-0!'
> >
<div className='w-full p-0'> {isLoadingOutgoingSapronakSummaries ? (
{isLoadingOutgoingSapronakSummaries ? ( <SapronakClosingSkeleton
<SapronakClosingSkeleton type='outgoing'
type='outgoing' columns={outgoingSapronaksColumns}
columns={outgoingSapronaksColumns} />
/> ) : isResponseSuccess(outgoingSapronakSummaries) &&
) : isResponseSuccess(outgoingSapronakSummaries) && outgoingSapronakSummaries.data.length === 0 ? (
outgoingSapronakSummaries.data.length === 0 ? ( <SapronakClosingSkeleton
<SapronakClosingSkeleton type='outgoing'
type='outgoing' columns={outgoingSapronaksColumns}
columns={outgoingSapronaksColumns} iconName='heroicons:chart-bar'
iconName='heroicons:chart-bar' title='Ringkasan Sapronak Keluar Tidak Ditemukan'
title='Ringkasan Sapronak Keluar Tidak Ditemukan' subtitle='Tidak ada ringkasan sapronak keluar untuk periode ini.'
subtitle='Tidak ada ringkasan sapronak keluar untuk periode ini.' />
/> ) : (
) : ( <Table<ClosingOutgoingSapronakSummary>
<Table<ClosingOutgoingSapronakSummary> data={
data={ isResponseSuccess(outgoingSapronakSummaries)
isResponseSuccess(outgoingSapronakSummaries) ? outgoingSapronakSummaries?.data
? outgoingSapronakSummaries?.data : []
: [] }
} columns={outgoingSapronaksColumns}
columns={outgoingSapronaksColumns} pageSize={tableFilterState.pageSize}
pageSize={tableFilterState.pageSize} onPageSizeChange={setPageSize}
onPageSizeChange={setPageSize} rowOptions={[10, 20, 50, 100]}
rowOptions={[10, 20, 50, 100]} page={
page={ isResponseSuccess(outgoingSapronakSummaries)
isResponseSuccess(outgoingSapronakSummaries) ? outgoingSapronakSummaries?.meta?.page
? outgoingSapronakSummaries?.meta?.page : 0
: 0 }
} totalItems={
totalItems={ isResponseSuccess(outgoingSapronakSummaries)
isResponseSuccess(outgoingSapronakSummaries) ? outgoingSapronakSummaries?.meta?.total_results
? outgoingSapronakSummaries?.meta?.total_results : 0
: 0 }
} onPageChange={setPage}
onPageChange={setPage} isLoading={isLoadingOutgoingSapronakSummaries}
isLoading={isLoadingOutgoingSapronakSummaries} sorting={sorting}
sorting={sorting} setSorting={setSorting}
setSorting={setSorting} rowSelection={rowSelection}
rowSelection={rowSelection} setRowSelection={setRowSelection}
setRowSelection={setRowSelection} className={{
className={{ containerClassName: 'w-full mb-0!',
containerClassName: cn({ tableWrapperClassName:
'w-full mb-20': 'overflow-x-auto rounded-tr-none rounded-tl-none',
isResponseSuccess(outgoingSapronakSummaries) && tableClassName: 'w-full table-auto text-sm',
outgoingSapronakSummaries?.data?.length === 0, headerRowClassName:
}), 'border-b border-b-gray-200 bg-gray-50',
}} headerColumnClassName:
/> 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
)} bodyRowClassName:
</div> 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
</Collapse> bodyColumnClassName:
</Card> 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}}
/>
)}
</Card>
</div>
); );
}; };
@@ -5,13 +5,11 @@ import { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { ColumnDef, SortingState } from '@tanstack/react-table'; import { ColumnDef, SortingState } from '@tanstack/react-table';
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 Card from '@/components/Card'; import Card from '@/components/Card';
import Collapse from '@/components/Collapse';
import { cn, formatDate, formatNumber } from '@/lib/helper'; import { formatDate, formatNumber } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
@@ -52,8 +50,6 @@ const ClosingOutgoingSapronaksTable = ({
ClosingApi.getAllOutgoingSapronakFetcher ClosingApi.getAllOutgoingSapronakFetcher
); );
const [open, setOpen] = useState(true);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({}); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
@@ -118,109 +114,91 @@ const ClosingOutgoingSapronaksTable = ({
} }
}, [sorting, updateFilter]); }, [sorting, updateFilter]);
useEffect(() => {
if (!open) {
setOpen(
isResponseSuccess(outgoingSapronaks)
? outgoingSapronaks.data.length > 0
: false
);
}
}, [outgoingSapronaks, isResponseSuccess]);
return ( return (
<Card <div className='w-full p-0 sm:p-3'>
className={{ <Card
wrapper: 'w-full', className={{
body: 'p-4 shadow', wrapper: 'w-full rounded-lg border-none',
}} body: 'p-0',
> title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
<Collapse collapsible: 'rounded-lg',
open={open} }}
onOpenChange={setOpen} variant='bordered'
title={ title='Sapronak Keluar'
<div className='card-actions p-4 justify-between items-center w-full'> collapsible
<div className='card-title'>Sapronak Keluar</div> defaultCollapsed={false}
<Icon
icon='material-symbols:keyboard-arrow-down'
width={24}
height={24}
className={cn('text-primary transition-transform', {
'-rotate-180': open,
})}
/>
</div>
}
className='w-full!'
titleClassName='w-full p-0!'
> >
<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-start items-end sm:items-center gap-4'>
<div className='w-full flex flex-col sm:flex-row justify-start items-end sm:items-center gap-4'> <DebouncedTextInput
<DebouncedTextInput name='search'
name='search' placeholder='Cari Sapronak Keluar'
placeholder='Cari Sapronak Keluar' value={tableFilterState.search}
value={tableFilterState.search} onChange={searchChangeHandler}
onChange={searchChangeHandler} className={{ wrapper: 'sm:max-w-3xs' }}
className={{ wrapper: 'sm:max-w-3xs' }} />
/>
</div>
</div> </div>
{isLoadingOutgoingSapronaks ? (
<SapronakClosingSkeleton
type='outgoing'
columns={outgoingSapronaksColumns}
/>
) : isResponseSuccess(outgoingSapronaks) &&
outgoingSapronaks.data.length === 0 ? (
<SapronakClosingSkeleton
type='outgoing'
columns={outgoingSapronaksColumns}
iconName='heroicons:chart-bar'
title='Data Sapronak Keluar Tidak Ditemukan'
subtitle='Tidak ada data sapronak keluar untuk periode ini.'
/>
) : (
<Table<ClosingOutgoingSapronak>
data={
isResponseSuccess(outgoingSapronaks)
? outgoingSapronaks?.data
: []
}
columns={outgoingSapronaksColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={
isResponseSuccess(outgoingSapronaks)
? outgoingSapronaks?.meta?.page
: 0
}
totalItems={
isResponseSuccess(outgoingSapronaks)
? outgoingSapronaks?.meta?.total_results
: 0
}
onPageChange={setPage}
isLoading={isLoadingOutgoingSapronaks}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(outgoingSapronaks) &&
outgoingSapronaks?.data?.length === 0,
}),
}}
/>
)}
</div> </div>
</Collapse>
</Card> {isLoadingOutgoingSapronaks ? (
<SapronakClosingSkeleton
type='outgoing'
columns={outgoingSapronaksColumns}
/>
) : isResponseSuccess(outgoingSapronaks) &&
outgoingSapronaks.data.length === 0 ? (
<SapronakClosingSkeleton
type='outgoing'
columns={outgoingSapronaksColumns}
iconName='heroicons:chart-bar'
title='Data Sapronak Keluar Tidak Ditemukan'
subtitle='Tidak ada data sapronak keluar untuk periode ini.'
/>
) : (
<Table<ClosingOutgoingSapronak>
data={
isResponseSuccess(outgoingSapronaks)
? outgoingSapronaks?.data
: []
}
columns={outgoingSapronaksColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={
isResponseSuccess(outgoingSapronaks)
? outgoingSapronaks?.meta?.page
: 0
}
totalItems={
isResponseSuccess(outgoingSapronaks)
? outgoingSapronaks?.meta?.total_results
: 0
}
onPageChange={setPage}
isLoading={isLoadingOutgoingSapronaks}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName:
'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}}
/>
)}
</Card>
</div>
); );
}; };