refactor(FE): Add skeleton loaders for inventory tables

This commit is contained in:
rstubryan
2026-03-02 13:31:23 +07:00
parent 9c4c750664
commit 1341b1ff53
6 changed files with 250 additions and 83 deletions
@@ -13,6 +13,7 @@ import { InventoryAdjustmentApi } from '@/services/api/inventory';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { InventoryAdjustment } from '@/types/api/inventory/adjustment'; import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
import StatusBadge from '@/components/helper/StatusBadge'; import StatusBadge from '@/components/helper/StatusBadge';
import InventoryAdjustmentTableSkeleton from '@/components/pages/inventory/adjustment/skeleton/InventoryAdjustmentTableSkeleton';
const InventoryAdjustmentTable = () => { const InventoryAdjustmentTable = () => {
const { const {
@@ -192,38 +193,55 @@ const InventoryAdjustmentTable = () => {
{/* Table Section */} {/* Table Section */}
<div className='flex flex-col mb-4'> <div className='flex flex-col mb-4'>
<Table<InventoryAdjustment> {isLoading ? (
data={ <div className='w-full flex flex-row justify-center items-center p-4'>
isResponseSuccess(inventoryAdjustments) <span className='loading loading-spinner loading-xl' />
? inventoryAdjustments?.data </div>
: [] ) : !isResponseSuccess(inventoryAdjustments) ||
} inventoryAdjustments.data?.length === 0 ? (
columns={inventoryAdjustmentsColumns} <div className='p-3'>
pageSize={tableFilterState.pageSize} <InventoryAdjustmentTableSkeleton
page={ columns={inventoryAdjustmentsColumns}
isResponseSuccess(inventoryAdjustments) icon={
? inventoryAdjustments?.meta?.page <Icon
: 0 icon='heroicons:document-text'
} className='text-white'
totalItems={ width={20}
isResponseSuccess(inventoryAdjustments) height={20}
? inventoryAdjustments?.meta?.total_results />
: 0 }
} />
onPageChange={setPage} </div>
onPageSizeChange={setPageSize} ) : (
isLoading={isLoading} <Table<InventoryAdjustment>
sorting={sorting} data={
setSorting={setSorting} isResponseSuccess(inventoryAdjustments)
className={{ ? inventoryAdjustments?.data
containerClassName: cn('p-3 mb-0', { : []
'w-full': }
isResponseSuccess(inventoryAdjustments) && columns={inventoryAdjustmentsColumns}
inventoryAdjustments?.data?.length === 0, pageSize={tableFilterState.pageSize}
}), page={
headerColumnClassName: 'text-nowrap', isResponseSuccess(inventoryAdjustments)
}} ? inventoryAdjustments?.meta?.page
/> : 0
}
totalItems={
isResponseSuccess(inventoryAdjustments)
? inventoryAdjustments?.meta?.total_results
: 0
}
onPageChange={setPage}
onPageSizeChange={setPageSize}
isLoading={isLoading}
sorting={sorting}
setSorting={setSorting}
className={{
containerClassName: cn('p-3 mb-0'),
headerColumnClassName: 'text-nowrap',
}}
/>
)}
</div> </div>
</div> </div>
); );
@@ -0,0 +1,37 @@
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
import Table from '@/components/Table';
import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
import { ColumnDef } from '@tanstack/react-table';
const InventoryAdjustmentTableSkeleton = ({
columns,
icon,
title = 'No Data Available',
subtitle = 'There is no inventory adjustment data displayed. Enter inventory adjustment data to get started.',
}: {
columns: ColumnDef<InventoryAdjustment>[];
icon: React.ReactNode;
title?: string;
subtitle?: string;
}) => {
return (
<div className='relative size-full'>
<Table
data={[]}
columns={columns}
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 InventoryAdjustmentTableSkeleton;
@@ -16,6 +16,7 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import PopoverButton from '@/components/popover/PopoverButton'; import PopoverButton from '@/components/popover/PopoverButton';
import PopoverContent from '@/components/popover/PopoverContent'; import PopoverContent from '@/components/popover/PopoverContent';
import MovementTableSkeleton from '@/components/pages/inventory/movement/skeleton/MovementTableSkeleton';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -198,27 +199,44 @@ const MovementTable = () => {
{/* Table Section */} {/* Table Section */}
<div className='flex flex-col mb-4'> <div className='flex flex-col mb-4'>
<Table<Movement> {isLoading ? (
data={isResponseSuccess(movements) ? movements?.data : []} <div className='w-full flex flex-row justify-center items-center p-4'>
columns={movementColumns} <span className='loading loading-spinner loading-xl' />
pageSize={tableFilterState.pageSize} </div>
page={isResponseSuccess(movements) ? movements?.meta?.page : 0} ) : !isResponseSuccess(movements) || movements.data?.length === 0 ? (
totalItems={ <div className='p-3'>
isResponseSuccess(movements) ? movements?.meta?.total_results : 0 <MovementTableSkeleton
} columns={movementColumns}
onPageChange={setPage} icon={
onPageSizeChange={setPageSize} <Icon
isLoading={isLoading} icon='heroicons:document-text'
sorting={sorting} className='text-white'
setSorting={setSorting} width={20}
className={{ height={20}
containerClassName: cn('p-3 mb-0', { />
'w-full': }
isResponseSuccess(movements) && movements?.data?.length === 0, />
}), </div>
headerColumnClassName: 'text-nowrap', ) : (
}} <Table<Movement>
/> data={isResponseSuccess(movements) ? movements?.data : []}
columns={movementColumns}
pageSize={tableFilterState.pageSize}
page={isResponseSuccess(movements) ? movements?.meta?.page : 0}
totalItems={
isResponseSuccess(movements) ? movements?.meta?.total_results : 0
}
onPageChange={setPage}
onPageSizeChange={setPageSize}
isLoading={isLoading}
sorting={sorting}
setSorting={setSorting}
className={{
containerClassName: cn('p-3 mb-0'),
headerColumnClassName: 'text-nowrap',
}}
/>
)}
</div> </div>
</div> </div>
); );
@@ -0,0 +1,37 @@
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
import Table from '@/components/Table';
import { Movement } from '@/types/api/inventory/movement';
import { ColumnDef } from '@tanstack/react-table';
const MovementTableSkeleton = ({
columns,
icon,
title = 'No Data Available',
subtitle = 'There is no movement data displayed. Enter movement data to get started.',
}: {
columns: ColumnDef<Movement>[];
icon: React.ReactNode;
title?: string;
subtitle?: string;
}) => {
return (
<div className='relative size-full'>
<Table
data={[]}
columns={columns}
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 MovementTableSkeleton;
@@ -15,6 +15,7 @@ import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import PopoverButton from '@/components/popover/PopoverButton'; import PopoverButton from '@/components/popover/PopoverButton';
import PopoverContent from '@/components/popover/PopoverContent'; import PopoverContent from '@/components/popover/PopoverContent';
import InventoryProductTableSkeleton from '@/components/pages/inventory/product/skeleton/InventoryProductTableSkeleton';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -206,36 +207,55 @@ const InventoryProductTable = () => {
{/* Table Section */} {/* Table Section */}
<div className='flex flex-col mb-4'> <div className='flex flex-col mb-4'>
<Table<InventoryProduct> {isLoading ? (
data={ <div className='w-full flex flex-row justify-center items-center p-4'>
isResponseSuccess(inventoryProducts) ? inventoryProducts?.data : [] <span className='loading loading-spinner loading-xl' />
} </div>
columns={columns} ) : !isResponseSuccess(inventoryProducts) ||
pageSize={tableFilterState.pageSize} inventoryProducts.data?.length === 0 ? (
page={ <div className='p-3'>
isResponseSuccess(inventoryProducts) <InventoryProductTableSkeleton
? inventoryProducts?.meta?.page columns={columns}
: 0 icon={
} <Icon
totalItems={ icon='heroicons:document-text'
isResponseSuccess(inventoryProducts) className='text-white'
? inventoryProducts?.meta?.total_results width={20}
: 0 height={20}
} />
onPageChange={setPage} }
onPageSizeChange={setPageSize} />
isLoading={isLoading} </div>
sorting={sorting} ) : (
setSorting={setSorting} <Table<InventoryProduct>
className={{ data={
containerClassName: cn('p-3 mb-0', { isResponseSuccess(inventoryProducts)
'w-full': ? inventoryProducts?.data
isResponseSuccess(inventoryProducts) && : []
inventoryProducts?.data?.length === 0, }
}), columns={columns}
headerColumnClassName: 'text-nowrap', pageSize={tableFilterState.pageSize}
}} page={
/> isResponseSuccess(inventoryProducts)
? inventoryProducts?.meta?.page
: 0
}
totalItems={
isResponseSuccess(inventoryProducts)
? inventoryProducts?.meta?.total_results
: 0
}
onPageChange={setPage}
onPageSizeChange={setPageSize}
isLoading={isLoading}
sorting={sorting}
setSorting={setSorting}
className={{
containerClassName: cn('p-3 mb-0'),
headerColumnClassName: 'text-nowrap',
}}
/>
)}
</div> </div>
</div> </div>
); );
@@ -0,0 +1,37 @@
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
import Table from '@/components/Table';
import { InventoryProduct } from '@/types/api/inventory/product';
import { ColumnDef } from '@tanstack/react-table';
const InventoryProductTableSkeleton = ({
columns,
icon,
title = 'No Data Available',
subtitle = 'There is no inventory product data displayed. Enter inventory product data to get started.',
}: {
columns: ColumnDef<InventoryProduct>[];
icon: React.ReactNode;
title?: string;
subtitle?: string;
}) => {
return (
<div className='relative size-full'>
<Table
data={[]}
columns={columns}
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 InventoryProductTableSkeleton;