feat(FE): Add skeleton components for closing pages

This commit is contained in:
rstubryan
2026-02-19 10:03:25 +07:00
parent 9c953ca382
commit 8fe19feaac
18 changed files with 1262 additions and 613 deletions
@@ -0,0 +1,36 @@
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
import Table from '@/components/Table';
import { ColumnDef } from '@tanstack/react-table';
const ClosingTabSkeleton = <T extends object>({
columns,
icon,
title,
subtitle,
}: {
columns: ColumnDef<T>[];
icon: React.ReactNode;
title: string;
subtitle: string;
}) => {
return (
<div className='relative size-full'>
<Table
data={[]}
columns={columns as ColumnDef<T>[]}
isLoading={true}
className={{
skeletonCellClassName: 'animate-none w-full h-5 bg-base-content/4',
headerColumnClassName: 'whitespace-nowrap',
containerClassName: 'mb-0 overflow-hidden',
tableWrapperClassName: 'overflow-hidden',
}}
/>
<div className='absolute inset-0 flex items-center justify-center'>
<DataStateSkeleton icon={icon} title={title} description={subtitle} />
</div>
</div>
);
};
export default ClosingTabSkeleton;
@@ -0,0 +1,40 @@
import { Icon } from '@iconify/react';
import Card from '@/components/Card';
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
const FinanceClosingSkeleton = ({
title = 'Data Keuangan Belum Tersedia',
subtitle = 'Tidak ada data keuangan untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<Card
variant='bordered'
className={{
wrapper: 'w-full',
body: 'p-8',
}}
>
<div className='flex items-center justify-center p-8'>
<DataStateSkeleton
icon={
<Icon
icon={iconName}
className='text-white'
width={20}
height={20}
/>
}
title={title}
description={subtitle}
/>
</div>
</Card>
);
};
export default FinanceClosingSkeleton;
@@ -0,0 +1,44 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { BaseExpeditionCost } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const HppExpeditionClosingSkeleton = ({
columns,
title = 'Data HPP Ekspedisi Belum Tersedia',
subtitle = 'Tidak ada data HPP ekspedisi untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns?: ColumnDef<BaseExpeditionCost>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
const defaultColumns: ColumnDef<BaseExpeditionCost>[] = [
{
id: 'id',
header: 'No',
},
{
id: 'expedition_vendor_name',
header: 'Nama Ekspedisi',
},
{
id: 'hpp_amount',
header: 'HPP Ekspedisi',
},
];
return (
<ClosingTabSkeleton<BaseExpeditionCost>
columns={columns || defaultColumns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default HppExpeditionClosingSkeleton;
@@ -0,0 +1,72 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { Overhead } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const OverheadClosingSkeleton = ({
columns,
title = 'Data Overhead Belum Tersedia',
subtitle = 'Tidak ada data overhead untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns?: ColumnDef<Overhead>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
const defaultColumns: ColumnDef<Overhead>[] = [
{
id: 'name',
header: 'Nama Overhead',
},
{
id: 'budget_quantity',
header: 'Budget Pengajuan - Jumlah',
},
{
id: 'budget_unit_price',
header: 'Budget Pengajuan - Harga Satuan',
},
{
id: 'budget_total_amount',
header: 'Budget Pengajuan - Total',
},
{
id: 'actual_quantity',
header: 'Realisasi - Jumlah',
},
{
id: 'actual_unit_price',
header: 'Realisasi - Harga Satuan',
},
{
id: 'actual_total_amount',
header: 'Realisasi - Total',
},
{
id: 'difference_quantity',
header: 'Selisih - Jumlah',
},
{
id: 'difference_unit_price',
header: 'Selisih - Harga Satuan',
},
{
id: 'difference_total_amount',
header: 'Selisih - Total',
},
];
return (
<ClosingTabSkeleton<Overhead>
columns={columns || defaultColumns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default OverheadClosingSkeleton;
@@ -0,0 +1,33 @@
import { Icon } from '@iconify/react';
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
const ProductionDataClosingSkeleton = ({
title = 'Data Produksi Belum Tersedia',
subtitle = 'Tidak ada data produksi untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<div className='w-full rounded-xl p-8 shadow-sm'>
<div className='flex items-center justify-center p-12'>
<DataStateSkeleton
icon={
<Icon
icon={iconName}
className='text-white'
width={20}
height={20}
/>
}
title={title}
description={subtitle}
/>
</div>
</div>
);
};
export default ProductionDataClosingSkeleton;
@@ -0,0 +1,84 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { BaseSales } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const SalesClosingSkeleton = ({
columns,
title = 'Data Penjualan Belum Tersedia',
subtitle = 'Tidak ada data penjualan untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns?: ColumnDef<BaseSales>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
const defaultColumns: ColumnDef<BaseSales>[] = [
{
id: 'realization_date',
header: 'Tanggal Realisasi',
},
{
id: 'age',
header: 'Umur',
},
{
id: 'do_number',
header: 'No. DO',
},
{
id: 'product',
header: 'Produk',
},
{
id: 'customer',
header: 'Customer',
},
{
id: 'qty',
header: 'Kuantitas',
},
{
id: 'weight',
header: 'Kg',
},
{
id: 'avg_weight',
header: 'AVG (Kg)',
},
{
id: 'sales_price',
header: 'Harga Sales (Rp)',
},
{
id: 'total_sales_price',
header: 'Total Sales (Rp)',
},
{
id: 'actual_price',
header: 'Harga Act (Rp)',
},
{
id: 'total_actual_price',
header: 'Total Act (Rp)',
},
{
id: 'kandang',
header: 'Kandang',
},
];
return (
<ClosingTabSkeleton<BaseSales>
columns={columns || defaultColumns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default SalesClosingSkeleton;
@@ -0,0 +1,72 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { RowSapronakCalculation } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const SapronakCalculationClosingSkeleton = ({
columns,
title = 'Data Perhitungan Sapronak Belum Tersedia',
subtitle = 'Tidak ada data perhitungan sapronak untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns?: ColumnDef<RowSapronakCalculation>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
const defaultColumns: ColumnDef<RowSapronakCalculation>[] = [
{
id: 'date',
header: 'Tanggal',
},
{
id: 'reference_number',
header: 'No. Referensi',
},
{
id: 'qty_in',
header: 'QTY Masuk',
},
{
id: 'qty_out',
header: 'QTY Keluar',
},
{
id: 'qty_used',
header: 'QTY Pakai',
},
{
id: 'balance',
header: 'Saldo',
},
{
id: 'unit_price_in',
header: 'Harga Masuk',
},
{
id: 'unit_price_out',
header: 'Harga Keluar',
},
{
id: 'total_price_in',
header: 'Total Harga Masuk',
},
{
id: 'total_price_out',
header: 'Total Harga Keluar',
},
];
return (
<ClosingTabSkeleton<RowSapronakCalculation>
columns={columns || defaultColumns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default SapronakCalculationClosingSkeleton;
@@ -0,0 +1,126 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { ClosingIncomingSapronak } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const SapronakClosingSkeleton = ({
columns,
type = 'incoming',
title,
subtitle,
iconName = 'heroicons:chart-bar',
}: {
columns?: ColumnDef<ClosingIncomingSapronak>[];
type?: 'incoming' | 'outgoing';
title?: string;
subtitle?: string;
iconName?: string;
}) => {
const defaultIncomingColumns: ColumnDef<ClosingIncomingSapronak>[] = [
{
id: '#',
header: '#',
},
{
id: 'date',
header: 'Tanggal',
},
{
id: 'reference_number',
header: 'No. Referensi',
},
{
id: 'transaction_type',
header: 'Jenis Transaksi',
},
{
id: 'product_name',
header: 'Produk',
},
{
id: 'product_category',
header: 'Kategori Produk',
},
{
id: 'source_warehouse',
header: 'Gudang Asal',
},
{
id: 'destination_warehouse',
header: 'Gudang Tujuan',
},
{
id: 'quantity',
header: 'Kuantitas',
},
{
id: 'notes',
header: 'Keterangan',
},
];
const defaultOutgoingColumns: ColumnDef<ClosingIncomingSapronak>[] = [
{
id: '#',
header: '#',
},
{
id: 'date',
header: 'Tanggal',
},
{
id: 'reference_number',
header: 'No. Referensi',
},
{
id: 'transaction_type',
header: 'Jenis Transaksi',
},
{
id: 'product_name',
header: 'Produk',
},
{
id: 'product_category',
header: 'Kategori Produk',
},
{
id: 'source_warehouse',
header: 'Gudang Asal',
},
{
id: 'quantity',
header: 'Kuantitas',
},
{
id: 'notes',
header: 'Keterangan',
},
];
const defaultTitle =
type === 'incoming'
? 'Data Sapronak Masuk Belum Tersedia'
: 'Data Sapronak Keluar Belum Tersedia';
const defaultSubtitle =
type === 'incoming'
? 'Silakan pilih periode atau filter untuk melihat data sapronak masuk.'
: 'Silakan pilih periode atau filter untuk melihat data sapronak keluar.';
return (
<ClosingTabSkeleton<ClosingIncomingSapronak>
columns={
columns ||
(type === 'incoming' ? defaultIncomingColumns : defaultOutgoingColumns)
}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title || defaultTitle}
subtitle={subtitle || defaultSubtitle}
/>
);
};
export default SapronakClosingSkeleton;
@@ -5,6 +5,7 @@ import useSWR from 'swr';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { formatNumber } from '@/lib/helper'; import { formatNumber } from '@/lib/helper';
import ProductionDataClosingSkeleton from '@/components/pages/closing/skeleton/ProductionDataClosingSkeleton';
interface ProductionDataClosingTabProps { interface ProductionDataClosingTabProps {
projectFlockId: number; projectFlockId: number;
@@ -22,18 +23,16 @@ const ProductionDataClosingTab = ({
); );
if (isLoading) { if (isLoading) {
return ( return <ProductionDataClosingSkeleton />;
<div className='w-full flex justify-center py-8'>
<span className='loading loading-spinner loading-lg' />
</div>
);
} }
if (!productionData || !isResponseSuccess(productionData)) { if (!productionData || !isResponseSuccess(productionData)) {
return ( return (
<div className='w-full text-center py-8 text-gray-500'> <ProductionDataClosingSkeleton
Gagal memuat data produksi. iconName='heroicons:exclamation-circle'
</div> title='Gagal Memuat Data Produksi'
subtitle='Terjadi kesalahan saat memuat data produksi. Silakan coba lagi.'
/>
); );
} }
@@ -7,6 +7,7 @@ import { HppItem, ProfitLossItem } from '@/types/api/closing';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useMemo } from 'react'; import { useMemo } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import FinanceClosingSkeleton from '@/components/pages/closing/skeleton/FinanceClosingSkeleton';
const FinanceClosingTable = ({ const FinanceClosingTable = ({
projectFlockId, projectFlockId,
@@ -82,316 +83,336 @@ const FinanceClosingTable = ({
return ( return (
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<> {isLoading ? (
<Card <FinanceClosingSkeleton />
variant='bordered' ) : !isResponseSuccess(finance) ? (
className={{ <FinanceClosingSkeleton
wrapper: 'w-full', iconName='heroicons:chart-bar'
}} title='Data Keuangan Tidak Ditemukan'
> subtitle='Tidak ada data keuangan untuk periode ini.'
<div className='grid grid-cols-2 gap-6'> />
<div className='flex flex-col gap-1'> ) : (
<div>Laba Rugi Brutto</div> <>
<div className='text-lg font-bold'> <Card
{isResponseSuccess(finance) variant='bordered'
? formatCurrency( className={{
finance.data.profit_loss.summary.gross_profit.amount wrapper: 'w-full',
) }}
: '-'} >
<div className='grid grid-cols-2 gap-6'>
<div className='flex flex-col gap-1'>
<div>Laba Rugi Brutto</div>
<div className='text-lg font-bold'>
{isResponseSuccess(finance)
? formatCurrency(
finance.data.profit_loss.summary.gross_profit.amount
)
: '-'}
</div>
</div>
<div className='flex flex-col gap-1'>
<div>Laba Rugi Netto</div>
<div className='text-lg font-bold'>
{isResponseSuccess(finance)
? formatCurrency(
finance.data.profit_loss.summary.net_profit.amount
)
: '-'}
</div>
</div> </div>
</div> </div>
<div className='flex flex-col gap-1'> </Card>
<div>Laba Rugi Netto</div> <Card
<div className='text-lg font-bold'> title='HPP Purchases'
{isResponseSuccess(finance) variant='bordered'
? formatCurrency( collapsible
finance.data.profit_loss.summary.net_profit.amount className={{
) wrapper: 'w-full',
: '-'} }}
</div> >
</div> <div className='mt-6 p-0 mb-0'>
</div> <Table<HppItem>
</Card> data={hppTableData}
<Card isLoading={isLoading}
title='HPP Purchases' columns={[
variant='bordered' {
collapsible header: 'No.',
className={{ enableSorting: false,
wrapper: 'w-full', accessorFn: (item, index) => {
}} if (item.code === 'custom_row') return '-';
> const dataRowsBefore = hppTableData
<div className='mt-6 p-0 mb-0'> .slice(0, index)
<Table<HppItem> .filter((row) => row.code !== 'custom_row').length;
data={hppTableData} return dataRowsBefore + 1;
isLoading={isLoading} },
columns={[ footer: (props) => {
{ return 'HPP';
header: 'No.', },
enableSorting: false,
accessorFn: (item, index) => {
if (item.code === 'custom_row') return '-';
const dataRowsBefore = hppTableData
.slice(0, index)
.filter((row) => row.code !== 'custom_row').length;
return dataRowsBefore + 1;
}, },
footer: (props) => { {
return 'HPP'; header: 'Jenis',
enableSorting: false,
accessorFn: (item) => formatTitleCase(item.label || '-'),
}, },
}, {
{ header: 'Budgeting',
header: 'Jenis', enableSorting: false,
enableSorting: false, columns: [
accessorFn: (item) => formatTitleCase(item.label || '-'), {
}, header: 'Rp/Ekor',
{ id: 'budgeting_rp_per_bird',
header: 'Budgeting', enableSorting: false,
enableSorting: false, accessorFn: (item) =>
columns: [ formatCurrency(item.budgeting?.rp_per_bird || 0),
{ footer: (props) => {
header: 'Rp/Ekor', return props.column.id === 'budgeting_rp_per_bird' &&
id: 'budgeting_rp_per_bird', isResponseSuccess(finance)
enableSorting: false, ? formatCurrency(
accessorFn: (item) => finance.data.hpp.summary?.budgeting
formatCurrency(item.budgeting?.rp_per_bird || 0), ?.rp_per_bird || 0
footer: (props) => { )
return props.column.id === 'budgeting_rp_per_bird' && : '-';
isResponseSuccess(finance) },
? formatCurrency(
finance.data.hpp.summary?.budgeting
?.rp_per_bird || 0
)
: '-';
}, },
}, {
{ header: 'Rp/Kg',
header: 'Rp/Kg', id: 'budgeting_rp_per_kg',
id: 'budgeting_rp_per_kg', enableSorting: false,
enableSorting: false, accessorFn: (item) =>
accessorFn: (item) => formatCurrency(item.budgeting?.rp_per_kg || 0),
formatCurrency(item.budgeting?.rp_per_kg || 0), footer: (props) => {
footer: (props) => { return props.column.id === 'budgeting_rp_per_kg' &&
return props.column.id === 'budgeting_rp_per_kg' && isResponseSuccess(finance)
isResponseSuccess(finance) ? formatCurrency(
? formatCurrency( finance.data.hpp.summary?.budgeting
finance.data.hpp.summary?.budgeting?.rp_per_kg || ?.rp_per_kg || 0
0 )
) : '-';
: '-'; },
}, },
}, {
{ header: 'Jumlah (Rp)',
header: 'Jumlah (Rp)', id: 'budgeting_amount',
id: 'budgeting_amount', enableSorting: false,
enableSorting: false, accessorFn: (item) =>
accessorFn: (item) => formatCurrency(item.budgeting?.amount || 0),
formatCurrency(item.budgeting?.amount || 0), footer: (props) => {
footer: (props) => { return props.column.id === 'budgeting_amount' &&
return props.column.id === 'budgeting_amount' && isResponseSuccess(finance)
isResponseSuccess(finance) ? formatCurrency(
? formatCurrency( finance.data.hpp.summary?.budgeting?.amount || 0
finance.data.hpp.summary?.budgeting?.amount || 0 )
) : '-';
: '-'; },
}, },
}, ],
], },
}, {
{ header: 'Realization',
header: 'Realization', enableSorting: false,
enableSorting: false, columns: [
columns: [ {
{ header: 'Rp/Ekor',
header: 'Rp/Ekor', id: 'realization_rp_per_bird',
id: 'realization_rp_per_bird', enableSorting: false,
enableSorting: false, accessorFn: (item) =>
accessorFn: (item) => formatCurrency(item.realization?.rp_per_bird || 0),
formatCurrency(item.realization?.rp_per_bird || 0), footer: (props) => {
footer: (props) => { return props.column.id ===
return props.column.id === 'realization_rp_per_bird' && 'realization_rp_per_bird' &&
isResponseSuccess(finance) isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.hpp.summary?.realization finance.data.hpp.summary?.realization
?.rp_per_bird || 0 ?.rp_per_bird || 0
) )
: '-'; : '-';
},
}, },
}, {
{ header: 'Rp/Kg',
header: 'Rp/Kg', id: 'realization_rp_per_kg',
id: 'realization_rp_per_kg', enableSorting: false,
enableSorting: false, accessorFn: (item) =>
accessorFn: (item) => formatCurrency(item.realization?.rp_per_kg || 0),
formatCurrency(item.realization?.rp_per_kg || 0), footer: (props) => {
footer: (props) => { return props.column.id === 'realization_rp_per_kg' &&
return props.column.id === 'realization_rp_per_kg' && isResponseSuccess(finance)
isResponseSuccess(finance) ? formatCurrency(
? formatCurrency( finance.data.hpp.summary?.realization
finance.data.hpp.summary?.realization ?.rp_per_kg || 0
?.rp_per_kg || 0 )
) : '-';
: '-'; },
}, },
}, {
{ header: 'Jumlah (Rp)',
header: 'Jumlah (Rp)', id: 'realization_amount',
id: 'realization_amount', enableSorting: false,
enableSorting: false, accessorFn: (item) =>
accessorFn: (item) => formatCurrency(item.realization?.amount || 0),
formatCurrency(item.realization?.amount || 0), footer: (props) => {
footer: (props) => { return props.column.id === 'realization_amount' &&
return props.column.id === 'realization_amount' && isResponseSuccess(finance)
isResponseSuccess(finance) ? formatCurrency(
? formatCurrency( finance.data.hpp.summary?.realization?.amount ||
finance.data.hpp.summary?.realization?.amount || 0 0
) )
: '-'; : '-';
},
}, },
}, ],
], },
}, ]}
]} renderCustomRow={(row) => {
renderCustomRow={(row) => { const rowData = row.original;
const rowData = row.original; if (rowData.code === 'custom_row') {
if (rowData.code === 'custom_row') { return (
return ( <tr
<tr key={row.id}
key={row.id} className={TABLE_DEFAULT_STYLING.bodyRowClassName}
className={TABLE_DEFAULT_STYLING.bodyRowClassName}
>
<td
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
></td>
<td
colSpan={7}
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
> >
<div className='font-bold'> <td
{formatTitleCase(rowData.label ?? '-')} className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
</div> ></td>
</td> <td
</tr> colSpan={7}
); className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
} >
return null; <div className='font-bold'>
}} {formatTitleCase(rowData.label ?? '-')}
renderFooter={isResponseSuccess(finance)} </div>
/> </td>
</div> </tr>
</Card> );
<Card }
title='Profit/Loss' return null;
variant='bordered' }}
collapsible renderFooter={isResponseSuccess(finance)}
className={{ />
wrapper: 'w-full', </div>
}} </Card>
> <Card
<div className='mt-6 p-0 mb-0'> title='Profit/Loss'
<Table<ProfitLossItem> variant='bordered'
data={profitLossTableData} collapsible
isLoading={isLoading} className={{
columns={[ wrapper: 'w-full',
{ }}
header: 'Jenis', >
enableSorting: false, <div className='mt-6 p-0 mb-0'>
accessorFn: (item) => item.label, <Table<ProfitLossItem>
cell: (item) => ( data={profitLossTableData}
<div className=''> isLoading={isLoading}
{formatTitleCase(item.row.original.label || '-')} columns={[
</div> {
), header: 'Jenis',
footer: () => ( enableSorting: false,
<div className='font-bold uppercase'>LABA RUGI NETTO</div> accessorFn: (item) => item.label,
), cell: (item) => (
}, <div className=''>
{ {formatTitleCase(item.row.original.label || '-')}
header: 'Rp/Ekor', </div>
enableSorting: false, ),
accessorFn: (item) => formatCurrency(item.rp_per_bird || 0), footer: () => (
footer: () => ( <div className='font-bold uppercase'>LABA RUGI NETTO</div>
<div className='font-bold'> ),
{isResponseSuccess(finance) },
? formatCurrency( {
finance.data.profit_loss.summary.net_profit header: 'Rp/Ekor',
.rp_per_bird || 0 enableSorting: false,
) accessorFn: (item) => formatCurrency(item.rp_per_bird || 0),
: formatCurrency(0)} footer: () => (
</div> <div className='font-bold'>
), {isResponseSuccess(finance)
}, ? formatCurrency(
{ finance.data.profit_loss.summary.net_profit
header: 'Rp/Kg', .rp_per_bird || 0
enableSorting: false, )
accessorFn: (item) => formatCurrency(item.rp_per_kg || 0), : formatCurrency(0)}
footer: () => ( </div>
<div className='font-bold'> ),
{isResponseSuccess(finance) },
? formatCurrency( {
finance.data.profit_loss.summary.net_profit header: 'Rp/Kg',
.rp_per_kg || 0 enableSorting: false,
) accessorFn: (item) => formatCurrency(item.rp_per_kg || 0),
: formatCurrency(0)} footer: () => (
</div> <div className='font-bold'>
), {isResponseSuccess(finance)
}, ? formatCurrency(
{ finance.data.profit_loss.summary.net_profit
header: 'Jumlah (Rp)', .rp_per_kg || 0
enableSorting: false, )
accessorFn: (item) => formatCurrency(item.amount || 0), : formatCurrency(0)}
footer: () => ( </div>
<div className='font-bold'> ),
{isResponseSuccess(finance) },
? formatCurrency( {
finance.data.profit_loss.summary.net_profit header: 'Jumlah (Rp)',
.amount || 0 enableSorting: false,
) accessorFn: (item) => formatCurrency(item.amount || 0),
: formatCurrency(0)} footer: () => (
</div> <div className='font-bold'>
), {isResponseSuccess(finance)
}, ? formatCurrency(
]} finance.data.profit_loss.summary.net_profit
renderCustomRow={(row) => { .amount || 0
const rowData = row.original; )
if (rowData.code === 'custom_row') { : formatCurrency(0)}
return ( </div>
<tr ),
key={row.id} },
className={TABLE_DEFAULT_STYLING.footerRowClassName} ]}
> renderCustomRow={(row) => {
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}> const rowData = row.original;
<div className='font-bold ps-6 uppercase'> if (rowData.code === 'custom_row') {
{formatTitleCase(rowData.label ?? '-')} return (
</div> <tr
</td> key={row.id}
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}> className={TABLE_DEFAULT_STYLING.footerRowClassName}
<div className='font-bold'> >
{formatCurrency(rowData.rp_per_bird ?? 0)} <td
</div> className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
</td> >
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}> <div className='font-bold ps-6 uppercase'>
<div className='font-bold'> {formatTitleCase(rowData.label ?? '-')}
{formatCurrency(rowData.rp_per_kg ?? 0)} </div>
</div> </td>
</td> <td
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}> className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
<div className='font-bold'> >
{formatCurrency(rowData.amount ?? 0)} <div className='font-bold'>
</div> {formatCurrency(rowData.rp_per_bird ?? 0)}
</td> </div>
</tr> </td>
); <td
} className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
return null; >
}} <div className='font-bold'>
className={{ {formatCurrency(rowData.rp_per_kg ?? 0)}
paginationClassName: 'hidden', </div>
}} </td>
renderFooter={isResponseSuccess(finance)} <td
/> className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
</div> >
</Card> <div className='font-bold'>
</> {formatCurrency(rowData.amount ?? 0)}
</div>
</td>
</tr>
);
}
return null;
}}
className={{
paginationClassName: 'hidden',
}}
renderFooter={isResponseSuccess(finance)}
/>
</div>
</Card>
</>
)}
</div> </div>
); );
}; };
@@ -10,6 +10,7 @@ import { BaseExpeditionCost } from '@/types/api/closing';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import HppExpeditionClosingSkeleton from '@/components/pages/closing/skeleton/HppExpeditionClosingSkeleton';
interface HppExpeditionClosingTableProps { interface HppExpeditionClosingTableProps {
projectFlockId: number; projectFlockId: number;
@@ -100,28 +101,39 @@ const HppExpeditionClosingTable = ({
body: 'p-0', body: 'p-0',
}} }}
> >
<Table {isLoading ? (
data={costOfRevenueExpeditionData} <HppExpeditionClosingSkeleton
columns={costOfRevenueExpeditionColumns} columns={costOfRevenueExpeditionColumns}
isLoading={isLoading} />
renderFooter={costOfRevenueExpeditionData.length > 0} ) : costOfRevenueExpeditionData.length === 0 ? (
className={{ <HppExpeditionClosingSkeleton
tableWrapperClassName: 'overflow-x-auto', columns={costOfRevenueExpeditionColumns}
tableClassName: 'w-full table-auto text-sm', iconName='heroicons:chart-bar'
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', <Table
bodyRowClassName: data={costOfRevenueExpeditionData}
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', columns={costOfRevenueExpeditionColumns}
bodyColumnClassName: isLoading={isLoading}
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', renderFooter={costOfRevenueExpeditionData.length > 0}
tableFooterClassName: className={{
'bg-gray-100 font-semibold border border-gray-200', tableWrapperClassName: 'overflow-x-auto',
footerRowClassName: 'border-t-2 border-gray-300', tableClassName: 'w-full table-auto text-sm',
footerColumnClassName: headerRowClassName: 'border-b border-b-gray-200',
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', 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> </Card>
</div> </div>
</section> </section>
@@ -14,6 +14,7 @@ import { ColumnDef } from '@tanstack/react-table';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useMemo } from 'react'; import { useMemo } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import OverheadClosingSkeleton from '@/components/pages/closing/skeleton/OverheadClosingSkeleton';
interface OverheadClosingTableProps { interface OverheadClosingTableProps {
projectFlockId: number; projectFlockId: number;
@@ -209,95 +210,108 @@ const OverheadClosingTable = ({
return ( return (
<> <>
<Card {isLoadingOverhead ? (
title='Pengeluaran Overhead' <OverheadClosingSkeleton columns={columns} />
collapsible ) : !isResponseSuccess(overhead) ||
defaultCollapsed={false} (!kandangId && overhead.data?.overheads.length === 0) ||
className={{ (kandangId && !isResponseSuccess(overheadKandang)) ? (
wrapper: 'w-full', <OverheadClosingSkeleton
body: 'p-4 shadow',
}}
>
<Table<Overhead>
data={
kandangId
? isResponseSuccess(overheadKandang)
? (overheadKandang.data?.overheads ?? [])
: []
: isResponseSuccess(overhead)
? (overhead.data?.overheads ?? [])
: []
}
columns={columns} columns={columns}
className={{ iconName='heroicons:chart-bar'
containerClassName: 'my-4', title='Data Overhead Tidak Ditemukan'
headerColumnClassName: cn( subtitle='Tidak ada data overhead untuk periode ini.'
TABLE_DEFAULT_STYLING.headerColumnClassName,
'whitespace-nowrap'
),
}}
isLoading={isLoadingOverhead}
renderFooter={
isResponseSuccess(overhead)
? overhead.data?.overheads.length > 0
: false
}
/> />
{kandangId && ( ) : (
<Card <Card
title='Pengeluaran Overhead'
collapsible
defaultCollapsed={false}
className={{
wrapper: 'w-full',
body: 'p-4 shadow',
}}
>
<Table<Overhead>
data={
kandangId
? isResponseSuccess(overheadKandang)
? (overheadKandang.data?.overheads ?? [])
: []
: isResponseSuccess(overhead)
? (overhead.data?.overheads ?? [])
: []
}
columns={columns}
className={{ className={{
wrapper: 'w-full', containerClassName: 'my-4',
body: 'p-4 shadow-button-soft border border-base-content/10 rounded-lg', headerColumnClassName: cn(
TABLE_DEFAULT_STYLING.headerColumnClassName,
'whitespace-nowrap'
),
}} }}
> isLoading={isLoadingOverhead}
<div className='flex flex-row gap-3 w-full justify-center items-stretch'> renderFooter={
<div className='flex flex-row items-center justify-between'> isResponseSuccess(overhead)
<h2 className='text-base font-bold'>Pembelian Kandang </h2> ? overhead.data?.overheads.length > 0
</div> : false
<div className='flex flex-col items-center justify-center'> }
<Icon icon='heroicons:equals' className='inline' /> />
</div> {kandangId && (
<div className='flex flex-col flex-1 gap-1.5'> <Card
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'> className={{
Populasi Akhir KANDANG{' '} wrapper: 'w-full',
<Icon icon='heroicons:x-mark' className='inline' /> Pemakaian body: 'p-4 shadow-button-soft border border-base-content/10 rounded-lg',
Di FARM }}
>
<div className='flex flex-row gap-3 w-full justify-center items-stretch'>
<div className='flex flex-row items-center justify-between'>
<h2 className='text-base font-bold'>Pembelian Kandang </h2>
</div> </div>
<hr className='w-full h-fit m-0 p-0 text-base-content/65' /> <div className='flex flex-col items-center justify-center'>
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'> <Icon icon='heroicons:equals' className='inline' />
Populasi Akhir Proyek </div>
<div className='flex flex-col flex-1 gap-1.5'>
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
Populasi Akhir KANDANG{' '}
<Icon icon='heroicons:x-mark' className='inline' />{' '}
Pemakaian Di FARM
</div>
<hr className='w-full h-fit m-0 p-0 text-base-content/65' />
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
Populasi Akhir Proyek
</div>
</div>
<div className='flex flex-col items-center justify-center'>
<Icon icon='heroicons:equals' className='inline' />
</div>
<div className='flex flex-col flex-1 gap-1.5'>
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
{formatNumber(chickinPopulation ?? 0)}
<Icon icon='heroicons:x-mark' className='inline' />
{formatCurrency(
isResponseSuccess(overhead)
? overhead.data?.total.actual_total_amount
: 0
)}
</div>
<hr className='w-full h-fit m-0 p-0 text-base-content/65' />
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
{formatNumber(generalInformation?.population ?? 0)}
</div>
</div>
<div className='flex flex-col items-center justify-center'>
<Icon icon='heroicons:equals' className='inline' />
</div>
<div className='flex flex-row items-center justify-between'>
<h2 className='text-base font-bold'>
{formatNumber(kandangTotal || 0)}
</h2>
</div> </div>
</div> </div>
<div className='flex flex-col items-center justify-center'> </Card>
<Icon icon='heroicons:equals' className='inline' /> )}
</div> </Card>
<div className='flex flex-col flex-1 gap-1.5'> )}
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
{formatNumber(chickinPopulation ?? 0)}
<Icon icon='heroicons:x-mark' className='inline' />
{formatCurrency(
isResponseSuccess(overhead)
? overhead.data?.total.actual_total_amount
: 0
)}
</div>
<hr className='w-full h-fit m-0 p-0 text-base-content/65' />
<div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
{formatNumber(generalInformation?.population ?? 0)}
</div>
</div>
<div className='flex flex-col items-center justify-center'>
<Icon icon='heroicons:equals' className='inline' />
</div>
<div className='flex flex-row items-center justify-between'>
<h2 className='text-base font-bold'>
{formatNumber(kandangTotal || 0)}
</h2>
</div>
</div>
</Card>
)}
</Card>
</> </>
); );
}; };
@@ -17,6 +17,7 @@ import { Kandang } from '@/types/api/master-data/kandang';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import SalesClosingSkeleton from '@/components/pages/closing/skeleton/SalesClosingSkeleton';
interface SalesClosingTableProps { interface SalesClosingTableProps {
projectFlockId: number; projectFlockId: number;
@@ -325,27 +326,36 @@ const SalesClosingTable = ({ projectFlockId }: SalesClosingTableProps) => {
body: 'p-0', body: 'p-0',
}} }}
> >
<Table {isLoading ? (
data={salesData} <SalesClosingSkeleton columns={salesColumns} />
columns={salesColumns} ) : salesData.length === 0 ? (
isLoading={isLoading} <SalesClosingSkeleton
renderFooter={salesData.length > 0} columns={salesColumns}
className={{ iconName='heroicons:chart-bar'
tableWrapperClassName: 'overflow-x-auto', />
tableClassName: 'w-full table-auto text-sm', ) : (
headerColumnClassName: <Table
'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', data={salesData}
bodyRowClassName: columns={salesColumns}
'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', isLoading={isLoading}
bodyColumnClassName: renderFooter={salesData.length > 0}
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', className={{
tableFooterClassName: tableWrapperClassName: 'overflow-x-auto',
'bg-gray-100 font-semibold border border-gray-200', tableClassName: 'w-full table-auto text-sm',
footerRowClassName: 'border-t-2 border-gray-300', headerColumnClassName:
footerColumnClassName: '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',
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', 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> </Card>
</div> </div>
</section> </section>
@@ -15,6 +15,7 @@ import { ClosingApi } from '@/services/api/closing';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { ClosingGeneralInformation } from '@/types/api/closing'; import { ClosingGeneralInformation } from '@/types/api/closing';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import SapronakCalculationClosingSkeleton from '@/components/pages/closing/skeleton/SapronakCalculationClosingSkeleton';
interface SapronakCalculationClosingTableProps { interface SapronakCalculationClosingTableProps {
projectFlockId: number; projectFlockId: number;
@@ -193,21 +194,32 @@ const SapronakCalculationClosingTable = ({
body: 'p-4 shadow', body: 'p-4 shadow',
}} }}
> >
<Table<RowSapronakCalculation> {isLoading ? (
data={ <SapronakCalculationClosingSkeleton />
isResponseSuccess(sapronakCalculation) ) : isResponseSuccess(sapronakCalculation) &&
? (sapronakCalculation.data?.doc?.rows ?? []) sapronakCalculation.data?.doc?.rows?.length === 0 ? (
: [] <SapronakCalculationClosingSkeleton
} iconName='heroicons:chart-bar'
columns={docColumns} title='Data Perhitungan Sapronak Tidak Ditemukan'
className={{ subtitle='Tidak ada data perhitungan sapronak untuk periode ini.'
containerClassName: 'my-4', />
}} ) : (
renderFooter={ <Table<RowSapronakCalculation>
isResponseSuccess(sapronakCalculation) && data={
sapronakCalculation.data?.doc?.rows.length > 0 isResponseSuccess(sapronakCalculation)
} ? (sapronakCalculation.data?.doc?.rows ?? [])
/> : []
}
columns={docColumns}
className={{
containerClassName: 'my-4',
}}
renderFooter={
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation.data?.doc?.rows?.length > 0
}
/>
)}
</Card> </Card>
<Card <Card
@@ -219,21 +231,32 @@ const SapronakCalculationClosingTable = ({
wrapper: 'w-full', wrapper: 'w-full',
}} }}
> >
<Table<RowSapronakCalculation> {isLoading ? (
data={ <SapronakCalculationClosingSkeleton />
isResponseSuccess(sapronakCalculation) ) : isResponseSuccess(sapronakCalculation) &&
? (sapronakCalculation.data?.ovk?.rows ?? []) sapronakCalculation.data?.ovk?.rows?.length === 0 ? (
: [] <SapronakCalculationClosingSkeleton
} iconName='heroicons:chart-bar'
columns={ovkColumns} title='Data Perhitungan Sapronak Tidak Ditemukan'
className={{ subtitle='Tidak ada data perhitungan sapronak untuk periode ini.'
containerClassName: 'my-4', />
}} ) : (
renderFooter={ <Table<RowSapronakCalculation>
isResponseSuccess(sapronakCalculation) && data={
sapronakCalculation.data?.ovk?.rows.length > 0 isResponseSuccess(sapronakCalculation)
} ? (sapronakCalculation.data?.ovk?.rows ?? [])
/> : []
}
columns={ovkColumns}
className={{
containerClassName: 'my-4',
}}
renderFooter={
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation.data?.ovk?.rows?.length > 0
}
/>
)}
</Card> </Card>
<Card <Card
@@ -245,21 +268,32 @@ const SapronakCalculationClosingTable = ({
wrapper: 'w-full', wrapper: 'w-full',
}} }}
> >
<Table<RowSapronakCalculation> {isLoading ? (
data={ <SapronakCalculationClosingSkeleton />
isResponseSuccess(sapronakCalculation) ) : isResponseSuccess(sapronakCalculation) &&
? (sapronakCalculation.data?.pakan?.rows ?? []) sapronakCalculation.data?.pakan?.rows?.length === 0 ? (
: [] <SapronakCalculationClosingSkeleton
} iconName='heroicons:chart-bar'
columns={pakanColumns} title='Data Perhitungan Sapronak Tidak Ditemukan'
className={{ subtitle='Tidak ada data perhitungan sapronak untuk periode ini.'
containerClassName: 'my-4', />
}} ) : (
renderFooter={ <Table<RowSapronakCalculation>
isResponseSuccess(sapronakCalculation) && data={
sapronakCalculation.data?.pakan?.rows.length > 0 isResponseSuccess(sapronakCalculation)
} ? (sapronakCalculation.data?.pakan?.rows ?? [])
/> : []
}
columns={pakanColumns}
className={{
containerClassName: 'my-4',
}}
renderFooter={
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation.data?.pakan?.rows?.length > 0
}
/>
)}
</Card> </Card>
</div> </div>
); );
@@ -15,6 +15,7 @@ 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';
import { ClosingIncomingSapronakSummary } from '@/types/api/closing'; import { ClosingIncomingSapronakSummary } from '@/types/api/closing';
import SapronakClosingSkeleton from '@/components/pages/closing/skeleton/SapronakClosingSkeleton';
interface ClosingIncomingSapronaksSummaryTableProps { interface ClosingIncomingSapronaksSummaryTableProps {
projectFlockId: number; projectFlockId: number;
@@ -131,40 +132,52 @@ const ClosingIncomingSapronaksSummaryTable = ({
titleClassName='w-full p-0!' titleClassName='w-full p-0!'
> >
<div className='w-full p-0'> <div className='w-full p-0'>
<Table<ClosingIncomingSapronakSummary> {isLoadingIncomingSapronakSummaries ? (
data={ <SapronakClosingSkeleton type='incoming' />
isResponseSuccess(incomingSapronakSummaries) ) : isResponseSuccess(incomingSapronakSummaries) &&
? incomingSapronakSummaries?.data incomingSapronakSummaries.data.length === 0 ? (
: [] <SapronakClosingSkeleton
} type='incoming'
columns={incomingSapronaksColumns} iconName='heroicons:chart-bar'
pageSize={tableFilterState.pageSize} title='Ringkasan Sapronak Masuk Tidak Ditemukan'
onPageSizeChange={setPageSize} subtitle='Tidak ada ringkasan sapronak masuk untuk periode ini.'
rowOptions={[10, 20, 50, 100]} />
page={ ) : (
isResponseSuccess(incomingSapronakSummaries) <Table<ClosingIncomingSapronakSummary>
? incomingSapronakSummaries?.meta?.page data={
: 0 isResponseSuccess(incomingSapronakSummaries)
} ? incomingSapronakSummaries?.data
totalItems={ : []
isResponseSuccess(incomingSapronakSummaries) }
? incomingSapronakSummaries?.meta?.total_results columns={incomingSapronaksColumns}
: 0 pageSize={tableFilterState.pageSize}
} onPageSizeChange={setPageSize}
onPageChange={setPage} rowOptions={[10, 20, 50, 100]}
isLoading={isLoadingIncomingSapronakSummaries} page={
sorting={sorting} isResponseSuccess(incomingSapronakSummaries)
setSorting={setSorting} ? incomingSapronakSummaries?.meta?.page
rowSelection={rowSelection} : 0
setRowSelection={setRowSelection} }
className={{ totalItems={
containerClassName: cn({ isResponseSuccess(incomingSapronakSummaries)
'w-full mb-20': ? incomingSapronakSummaries?.meta?.total_results
isResponseSuccess(incomingSapronakSummaries) && : 0
incomingSapronakSummaries?.data?.length === 0, }
}), onPageChange={setPage}
}} isLoading={isLoadingIncomingSapronakSummaries}
/> sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(incomingSapronakSummaries) &&
incomingSapronakSummaries?.data?.length === 0,
}),
}}
/>
)}
</div> </div>
</Collapse> </Collapse>
</Card> </Card>
@@ -16,6 +16,7 @@ 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';
import { ClosingIncomingSapronak } from '@/types/api/closing'; import { ClosingIncomingSapronak } from '@/types/api/closing';
import SapronakClosingSkeleton from '@/components/pages/closing/skeleton/SapronakClosingSkeleton';
interface ClosingIncomingSapronaksTableProps { interface ClosingIncomingSapronaksTableProps {
projectFlockId: number; projectFlockId: number;
@@ -167,40 +168,52 @@ const ClosingIncomingSapronaksTable = ({
</div> </div>
</div> </div>
<Table<ClosingIncomingSapronak> {isLoadingIncomingSapronaks ? (
data={ <SapronakClosingSkeleton type='incoming' />
isResponseSuccess(incomingSapronaks) ) : isResponseSuccess(incomingSapronaks) &&
? incomingSapronaks?.data incomingSapronaks.data.length === 0 ? (
: [] <SapronakClosingSkeleton
} type='incoming'
columns={incomingSapronaksColumns} iconName='heroicons:chart-bar'
pageSize={tableFilterState.pageSize} title='Data Sapronak Masuk Tidak Ditemukan'
onPageSizeChange={setPageSize} subtitle='Tidak ada data sapronak masuk untuk periode ini.'
rowOptions={[10, 20, 50, 100]} />
page={ ) : (
isResponseSuccess(incomingSapronaks) <Table<ClosingIncomingSapronak>
? incomingSapronaks?.meta?.page data={
: 0 isResponseSuccess(incomingSapronaks)
} ? incomingSapronaks?.data
totalItems={ : []
isResponseSuccess(incomingSapronaks) }
? incomingSapronaks?.meta?.total_results columns={incomingSapronaksColumns}
: 0 pageSize={tableFilterState.pageSize}
} onPageSizeChange={setPageSize}
onPageChange={setPage} rowOptions={[10, 20, 50, 100]}
isLoading={isLoadingIncomingSapronaks} page={
sorting={sorting} isResponseSuccess(incomingSapronaks)
setSorting={setSorting} ? incomingSapronaks?.meta?.page
rowSelection={rowSelection} : 0
setRowSelection={setRowSelection} }
className={{ totalItems={
containerClassName: cn({ isResponseSuccess(incomingSapronaks)
'w-full mb-20': ? incomingSapronaks?.meta?.total_results
isResponseSuccess(incomingSapronaks) && : 0
incomingSapronaks?.data?.length === 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> </Collapse>
</Card> </Card>
@@ -15,6 +15,7 @@ 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';
import { ClosingOutgoingSapronakSummary } from '@/types/api/closing'; import { ClosingOutgoingSapronakSummary } from '@/types/api/closing';
import SapronakClosingSkeleton from '@/components/pages/closing/skeleton/SapronakClosingSkeleton';
interface ClosingOutgoingSapronaksSummaryTableProps { interface ClosingOutgoingSapronaksSummaryTableProps {
projectFlockId: number; projectFlockId: number;
@@ -131,40 +132,52 @@ const ClosingOutgoingSapronaksSummaryTable = ({
titleClassName='w-full p-0!' titleClassName='w-full p-0!'
> >
<div className='w-full p-0'> <div className='w-full p-0'>
<Table<ClosingOutgoingSapronakSummary> {isLoadingOutgoingSapronakSummaries ? (
data={ <SapronakClosingSkeleton type='outgoing' />
isResponseSuccess(outgoingSapronakSummaries) ) : isResponseSuccess(outgoingSapronakSummaries) &&
? outgoingSapronakSummaries?.data outgoingSapronakSummaries.data.length === 0 ? (
: [] <SapronakClosingSkeleton
} type='outgoing'
columns={outgoingSapronaksColumns} iconName='heroicons:chart-bar'
pageSize={tableFilterState.pageSize} title='Ringkasan Sapronak Keluar Tidak Ditemukan'
onPageSizeChange={setPageSize} subtitle='Tidak ada ringkasan sapronak keluar untuk periode ini.'
rowOptions={[10, 20, 50, 100]} />
page={ ) : (
isResponseSuccess(outgoingSapronakSummaries) <Table<ClosingOutgoingSapronakSummary>
? outgoingSapronakSummaries?.meta?.page data={
: 0 isResponseSuccess(outgoingSapronakSummaries)
} ? outgoingSapronakSummaries?.data
totalItems={ : []
isResponseSuccess(outgoingSapronakSummaries) }
? outgoingSapronakSummaries?.meta?.total_results columns={outgoingSapronaksColumns}
: 0 pageSize={tableFilterState.pageSize}
} onPageSizeChange={setPageSize}
onPageChange={setPage} rowOptions={[10, 20, 50, 100]}
isLoading={isLoadingOutgoingSapronakSummaries} page={
sorting={sorting} isResponseSuccess(outgoingSapronakSummaries)
setSorting={setSorting} ? outgoingSapronakSummaries?.meta?.page
rowSelection={rowSelection} : 0
setRowSelection={setRowSelection} }
className={{ totalItems={
containerClassName: cn({ isResponseSuccess(outgoingSapronakSummaries)
'w-full mb-20': ? outgoingSapronakSummaries?.meta?.total_results
isResponseSuccess(outgoingSapronakSummaries) && : 0
outgoingSapronakSummaries?.data?.length === 0, }
}), onPageChange={setPage}
}} isLoading={isLoadingOutgoingSapronakSummaries}
/> sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(outgoingSapronakSummaries) &&
outgoingSapronakSummaries?.data?.length === 0,
}),
}}
/>
)}
</div> </div>
</Collapse> </Collapse>
</Card> </Card>
@@ -16,6 +16,7 @@ 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';
import { ClosingOutgoingSapronak } from '@/types/api/closing'; import { ClosingOutgoingSapronak } from '@/types/api/closing';
import SapronakClosingSkeleton from '@/components/pages/closing/skeleton/SapronakClosingSkeleton';
interface ClosingOutgoingSapronaksTableProps { interface ClosingOutgoingSapronaksTableProps {
projectFlockId: number; projectFlockId: number;
@@ -167,40 +168,52 @@ const ClosingOutgoingSapronaksTable = ({
</div> </div>
</div> </div>
<Table<ClosingOutgoingSapronak> {isLoadingOutgoingSapronaks ? (
data={ <SapronakClosingSkeleton type='outgoing' />
isResponseSuccess(outgoingSapronaks) ) : isResponseSuccess(outgoingSapronaks) &&
? outgoingSapronaks?.data outgoingSapronaks.data.length === 0 ? (
: [] <SapronakClosingSkeleton
} type='outgoing'
columns={outgoingSapronaksColumns} iconName='heroicons:chart-bar'
pageSize={tableFilterState.pageSize} title='Data Sapronak Keluar Tidak Ditemukan'
onPageSizeChange={setPageSize} subtitle='Tidak ada data sapronak keluar untuk periode ini.'
rowOptions={[10, 20, 50, 100]} />
page={ ) : (
isResponseSuccess(outgoingSapronaks) <Table<ClosingOutgoingSapronak>
? outgoingSapronaks?.meta?.page data={
: 0 isResponseSuccess(outgoingSapronaks)
} ? outgoingSapronaks?.data
totalItems={ : []
isResponseSuccess(outgoingSapronaks) }
? outgoingSapronaks?.meta?.total_results columns={outgoingSapronaksColumns}
: 0 pageSize={tableFilterState.pageSize}
} onPageSizeChange={setPageSize}
onPageChange={setPage} rowOptions={[10, 20, 50, 100]}
isLoading={isLoadingOutgoingSapronaks} page={
sorting={sorting} isResponseSuccess(outgoingSapronaks)
setSorting={setSorting} ? outgoingSapronaks?.meta?.page
rowSelection={rowSelection} : 0
setRowSelection={setRowSelection} }
className={{ totalItems={
containerClassName: cn({ isResponseSuccess(outgoingSapronaks)
'w-full mb-20': ? outgoingSapronaks?.meta?.total_results
isResponseSuccess(outgoingSapronaks) && : 0
outgoingSapronaks?.data?.length === 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> </Collapse>
</Card> </Card>