mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
refactor(FE-344): update pagination UI and table footer handling
This commit is contained in:
+302
-212
@@ -1,7 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ReactNode } from 'react';
|
import { ChangeEventHandler, ReactNode } from 'react';
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
@@ -17,16 +19,18 @@ const PaginationButton = ({
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}) => (
|
}) => (
|
||||||
<button
|
<Button
|
||||||
className={cn(
|
variant='ghost'
|
||||||
'join-item btn btn-ghost p-2.5 rounded-lg text-sm font-medium text-gray-500 aspect-square',
|
color='none'
|
||||||
'disabled:text-gray-700 disabled:pointer-events-auto! disabled:cursor-not-allowed! disabled:bg-gray-50 disabled:active:translate-y-0'
|
|
||||||
)}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
className={cn(
|
||||||
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
||||||
|
'disabled:text-primary disabled:pointer-events-auto! disabled:cursor-not-allowed! disabled:bg-primary/10 disabled:active:translate-y-0'
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
const EtcPaginationButton = ({
|
const EtcPaginationButton = ({
|
||||||
@@ -48,7 +52,7 @@ const EtcPaginationButton = ({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role='button'
|
role='button'
|
||||||
className={cn(
|
className={cn(
|
||||||
'join-item btn btn-ghost p-2.5 rounded-lg text-sm font-medium text-gray-500 aspect-square'
|
'join-item btn btn-ghost p-2.5 rounded-lg! text-sm font-medium text-gray-500 aspect-square'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
...
|
...
|
||||||
@@ -57,7 +61,7 @@ const EtcPaginationButton = ({
|
|||||||
<div className='dropdown-content'>
|
<div className='dropdown-content'>
|
||||||
<ul
|
<ul
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className='menu bg-base-100 rounded-lg z-1 w-fit min-w-max max-h-64 p-1 shadow-sm mb-2 overflow-y-auto flex-nowrap'
|
className='menu bg-base-100 rounded-lg! z-1 w-fit min-w-max max-h-64 p-1 shadow-sm mb-2 overflow-y-auto flex-nowrap'
|
||||||
>
|
>
|
||||||
{pages.map((pageNumber) => (
|
{pages.map((pageNumber) => (
|
||||||
<li key={pageNumber}>
|
<li key={pageNumber}>
|
||||||
@@ -76,7 +80,7 @@ const EtcPaginationButton = ({
|
|||||||
<button
|
<button
|
||||||
disabled
|
disabled
|
||||||
className={cn(
|
className={cn(
|
||||||
'join-item btn btn-ghost p-2.5 rounded-lg text-sm font-medium text-gray-500 aspect-square'
|
'join-item btn btn-ghost p-2.5 rounded-lg! text-sm font-medium text-gray-500 aspect-square'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
...
|
...
|
||||||
@@ -90,16 +94,20 @@ const Pagination = ({
|
|||||||
currentPage = 1,
|
currentPage = 1,
|
||||||
totalItems = 0,
|
totalItems = 0,
|
||||||
itemsPerPage = 10,
|
itemsPerPage = 10,
|
||||||
|
rowOptions = [10, 20, 50, 100],
|
||||||
onPageChange,
|
onPageChange,
|
||||||
onPrevPage = () => {},
|
onPrevPage = () => {},
|
||||||
onNextPage = () => {},
|
onNextPage = () => {},
|
||||||
|
onRowChange,
|
||||||
}: {
|
}: {
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
totalItems: number;
|
totalItems: number;
|
||||||
itemsPerPage: number;
|
itemsPerPage: number;
|
||||||
|
rowOptions?: number[];
|
||||||
onPageChange: (pageNumber: number) => void;
|
onPageChange: (pageNumber: number) => void;
|
||||||
onPrevPage: () => void;
|
onPrevPage: () => void;
|
||||||
onNextPage: () => void;
|
onNextPage: () => void;
|
||||||
|
onRowChange?: (row: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const totalPages =
|
const totalPages =
|
||||||
Math.ceil(totalItems / itemsPerPage) === 0
|
Math.ceil(totalItems / itemsPerPage) === 0
|
||||||
@@ -107,30 +115,139 @@ const Pagination = ({
|
|||||||
: Math.ceil(totalItems / itemsPerPage);
|
: Math.ceil(totalItems / itemsPerPage);
|
||||||
|
|
||||||
const pageChangeHandler = (pageNumber: number) => onPageChange(pageNumber);
|
const pageChangeHandler = (pageNumber: number) => onPageChange(pageNumber);
|
||||||
|
const firstPageClickHandler = () => onPageChange(1);
|
||||||
|
const lastPageClickHandler = () => onPageChange(totalPages);
|
||||||
|
|
||||||
|
const rowChangeHandler: ChangeEventHandler<HTMLSelectElement> = (e) => {
|
||||||
|
onRowChange?.(Number(e.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
const DisplayedRowCountSelect = () => (
|
||||||
|
<div className='flex flex-row items-center gap-4'>
|
||||||
|
<span className='text-sm font-medium text-base-content/50'>Showing</span>
|
||||||
|
|
||||||
|
<select
|
||||||
|
defaultValue={itemsPerPage}
|
||||||
|
onChange={rowChangeHandler}
|
||||||
|
className='select select-xs w-fit pl-3 pr-7 text-base-content/50'
|
||||||
|
>
|
||||||
|
{rowOptions.map((rowOption, rowOptionIdx) => (
|
||||||
|
<option
|
||||||
|
key={rowOptionIdx}
|
||||||
|
value={rowOption}
|
||||||
|
className='text-base-content active:text-neutral-content'
|
||||||
|
>
|
||||||
|
{rowOption} Per page
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const GoToFirstPageButton = () => (
|
||||||
|
<Button
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
onClick={firstPageClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
className={cn(
|
||||||
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
||||||
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:chevron-double-left'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className='text-gray-400 group-disabled:text-gray-300'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const PrevPageButton = () => (
|
||||||
|
<Button
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
onClick={onPrevPage}
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
className={cn(
|
||||||
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
||||||
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:chevron-left'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className='text-gray-400 group-disabled:text-gray-300'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const GoToLastPageButton = () => (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
onClick={lastPageClickHandler}
|
||||||
|
className={cn(
|
||||||
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
||||||
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:chevron-double-right'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className='text-gray-400 group-disabled:text-gray-300'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const NextPageButton = () => (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
onClick={onNextPage}
|
||||||
|
className={cn(
|
||||||
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
||||||
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:chevron-right'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className='text-gray-400 group-disabled:text-gray-300'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const PageInfo = () => (
|
||||||
|
<span className='text-nowrap text-sm font-medium text-base-content/50'>
|
||||||
|
Page {currentPage} of {totalPages}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='@container'>
|
||||||
<div className='join w-full justify-between items-center gap-3'>
|
<div className='flex flex-row justify-center items-center'>
|
||||||
<button
|
<div className='hidden @md:block'>
|
||||||
disabled={currentPage === 1}
|
<DisplayedRowCountSelect />
|
||||||
onClick={onPrevPage}
|
</div>
|
||||||
className={cn(
|
|
||||||
'join-item btn btn-outline group px-3 py-2 text-sm font-semibold rounded-lg border border-gray-300 shadow-xs hidden sm:flex justify-center items-center gap-1.5',
|
|
||||||
'disabled:bg-[initial]! disabled:text-gray-400 disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='uil:arrow-left'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
className='text-gray-400 group-disabled:text-gray-300'
|
|
||||||
/>{' '}
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{totalPages <= 7 && (
|
<div className='join w-full justify-end @md:justify-center items-center gap-0.5'>
|
||||||
<div className='join-item join gap-0.5'>
|
<div className='hidden @md:block'>
|
||||||
{range(1, totalPages).map((pageNumber) => (
|
<GoToFirstPageButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='hidden @md:block'>
|
||||||
|
<PrevPageButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{totalPages <= 7 &&
|
||||||
|
range(1, totalPages).map((pageNumber) => (
|
||||||
<PaginationButton
|
<PaginationButton
|
||||||
key={pageNumber}
|
key={pageNumber}
|
||||||
content={pageNumber}
|
content={pageNumber}
|
||||||
@@ -138,195 +255,168 @@ const Pagination = ({
|
|||||||
onClick={() => pageChangeHandler(pageNumber)}
|
onClick={() => pageChangeHandler(pageNumber)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages > 7 && (
|
{totalPages > 7 && (
|
||||||
<div className='join-item join gap-0.5'>
|
<>
|
||||||
<PaginationButton
|
|
||||||
content={1}
|
|
||||||
disabled={currentPage === 1}
|
|
||||||
onClick={() => pageChangeHandler(1)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{totalPages >= 2 &&
|
|
||||||
(currentPage <= 3 || currentPage >= totalPages - 2) && (
|
|
||||||
<PaginationButton
|
|
||||||
content={2}
|
|
||||||
disabled={currentPage === 2}
|
|
||||||
onClick={() => pageChangeHandler(2)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 2 &&
|
|
||||||
currentPage > 3 &&
|
|
||||||
currentPage < totalPages - 2 && (
|
|
||||||
<EtcPaginationButton
|
|
||||||
startPage={2}
|
|
||||||
endPage={currentPage - 2}
|
|
||||||
onPageItemClick={pageChangeHandler}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 3 &&
|
|
||||||
(currentPage <= 4 || currentPage >= totalPages - 2) &&
|
|
||||||
currentPage !== totalPages - 2 && (
|
|
||||||
<PaginationButton
|
|
||||||
content={3}
|
|
||||||
disabled={currentPage === 3}
|
|
||||||
onClick={() => pageChangeHandler(3)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 7 &&
|
|
||||||
(currentPage <= 2 || currentPage >= totalPages - 2) && (
|
|
||||||
<EtcPaginationButton
|
|
||||||
startPage={
|
|
||||||
currentPage <= 2
|
|
||||||
? currentPage + 2
|
|
||||||
: currentPage === totalPages - 2
|
|
||||||
? 3
|
|
||||||
: currentPage >= totalPages - 1
|
|
||||||
? 4
|
|
||||||
: 1
|
|
||||||
}
|
|
||||||
endPage={
|
|
||||||
currentPage <= 2 || currentPage >= totalPages - 1
|
|
||||||
? totalPages - 3
|
|
||||||
: currentPage === totalPages - 2
|
|
||||||
? totalPages - 4
|
|
||||||
: 2
|
|
||||||
}
|
|
||||||
onPageItemClick={pageChangeHandler}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 3 &&
|
|
||||||
currentPage > 4 &&
|
|
||||||
currentPage < totalPages - 1 && (
|
|
||||||
<PaginationButton
|
|
||||||
content={currentPage - 1}
|
|
||||||
onClick={() => pageChangeHandler(currentPage - 1)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 7 &&
|
|
||||||
currentPage > 3 &&
|
|
||||||
currentPage < totalPages - 2 && (
|
|
||||||
<PaginationButton content={currentPage} disabled />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 5 &&
|
|
||||||
currentPage > 2 &&
|
|
||||||
currentPage < totalPages - 2 && (
|
|
||||||
<PaginationButton
|
|
||||||
content={currentPage + 1}
|
|
||||||
onClick={() => pageChangeHandler(currentPage + 1)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 5 &&
|
|
||||||
(currentPage <= 2 || currentPage >= totalPages - 2) && (
|
|
||||||
<PaginationButton
|
|
||||||
content={totalPages - 2}
|
|
||||||
disabled={currentPage === totalPages - 2}
|
|
||||||
onClick={() => pageChangeHandler(totalPages - 2)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 6 &&
|
|
||||||
currentPage > 2 &&
|
|
||||||
currentPage < totalPages - 3 && (
|
|
||||||
<EtcPaginationButton
|
|
||||||
startPage={
|
|
||||||
currentPage <= 3
|
|
||||||
? currentPage + 2
|
|
||||||
: currentPage >= 4
|
|
||||||
? currentPage + 2
|
|
||||||
: 1
|
|
||||||
}
|
|
||||||
endPage={
|
|
||||||
currentPage <= 3
|
|
||||||
? totalPages - 2
|
|
||||||
: currentPage >= 4
|
|
||||||
? totalPages - 1
|
|
||||||
: 0
|
|
||||||
}
|
|
||||||
onPageItemClick={pageChangeHandler}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 6 &&
|
|
||||||
(currentPage <= 3 || currentPage >= totalPages - 3) && (
|
|
||||||
<PaginationButton
|
|
||||||
content={totalPages - 1}
|
|
||||||
disabled={currentPage === totalPages - 1}
|
|
||||||
onClick={() => pageChangeHandler(totalPages - 1)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalPages >= 7 && (
|
|
||||||
<PaginationButton
|
<PaginationButton
|
||||||
content={totalPages}
|
content={1}
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === 1}
|
||||||
onClick={() => pageChangeHandler(totalPages)}
|
onClick={() => pageChangeHandler(1)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
{totalPages >= 2 &&
|
||||||
disabled={currentPage === totalPages}
|
(currentPage <= 3 || currentPage >= totalPages - 2) && (
|
||||||
onClick={onNextPage}
|
<PaginationButton
|
||||||
className={cn(
|
content={2}
|
||||||
'join-item btn btn-outline group px-3 py-2 text-sm font-semibold rounded-lg border border-gray-300 shadow-xs hidden sm:flex justify-center items-center gap-1.5',
|
disabled={currentPage === 2}
|
||||||
'disabled:bg-[initial]! disabled:text-gray-400 disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
onClick={() => pageChangeHandler(2)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 2 &&
|
||||||
|
currentPage > 3 &&
|
||||||
|
currentPage < totalPages - 2 && (
|
||||||
|
<EtcPaginationButton
|
||||||
|
startPage={2}
|
||||||
|
endPage={currentPage - 2}
|
||||||
|
onPageItemClick={pageChangeHandler}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 3 &&
|
||||||
|
(currentPage <= 4 || currentPage >= totalPages - 2) &&
|
||||||
|
currentPage !== totalPages - 2 && (
|
||||||
|
<PaginationButton
|
||||||
|
content={3}
|
||||||
|
disabled={currentPage === 3}
|
||||||
|
onClick={() => pageChangeHandler(3)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 7 &&
|
||||||
|
(currentPage <= 2 || currentPage >= totalPages - 2) && (
|
||||||
|
<EtcPaginationButton
|
||||||
|
startPage={
|
||||||
|
currentPage <= 2
|
||||||
|
? currentPage + 2
|
||||||
|
: currentPage === totalPages - 2
|
||||||
|
? 3
|
||||||
|
: currentPage >= totalPages - 1
|
||||||
|
? 4
|
||||||
|
: 1
|
||||||
|
}
|
||||||
|
endPage={
|
||||||
|
currentPage <= 2 || currentPage >= totalPages - 1
|
||||||
|
? totalPages - 3
|
||||||
|
: currentPage === totalPages - 2
|
||||||
|
? totalPages - 4
|
||||||
|
: 2
|
||||||
|
}
|
||||||
|
onPageItemClick={pageChangeHandler}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 3 &&
|
||||||
|
currentPage > 4 &&
|
||||||
|
currentPage < totalPages - 1 && (
|
||||||
|
<PaginationButton
|
||||||
|
content={currentPage - 1}
|
||||||
|
onClick={() => pageChangeHandler(currentPage - 1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 7 &&
|
||||||
|
currentPage > 3 &&
|
||||||
|
currentPage < totalPages - 2 && (
|
||||||
|
<PaginationButton content={currentPage} disabled />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 5 &&
|
||||||
|
currentPage > 2 &&
|
||||||
|
currentPage < totalPages - 2 && (
|
||||||
|
<PaginationButton
|
||||||
|
content={currentPage + 1}
|
||||||
|
onClick={() => pageChangeHandler(currentPage + 1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 5 &&
|
||||||
|
(currentPage <= 2 || currentPage >= totalPages - 2) && (
|
||||||
|
<PaginationButton
|
||||||
|
content={totalPages - 2}
|
||||||
|
disabled={currentPage === totalPages - 2}
|
||||||
|
onClick={() => pageChangeHandler(totalPages - 2)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 6 &&
|
||||||
|
currentPage > 2 &&
|
||||||
|
currentPage < totalPages - 3 && (
|
||||||
|
<EtcPaginationButton
|
||||||
|
startPage={
|
||||||
|
currentPage <= 3
|
||||||
|
? currentPage + 2
|
||||||
|
: currentPage >= 4
|
||||||
|
? currentPage + 2
|
||||||
|
: 1
|
||||||
|
}
|
||||||
|
endPage={
|
||||||
|
currentPage <= 3
|
||||||
|
? totalPages - 2
|
||||||
|
: currentPage >= 4
|
||||||
|
? totalPages - 1
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
onPageItemClick={pageChangeHandler}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 6 &&
|
||||||
|
(currentPage <= 3 || currentPage >= totalPages - 3) && (
|
||||||
|
<PaginationButton
|
||||||
|
content={totalPages - 1}
|
||||||
|
disabled={currentPage === totalPages - 1}
|
||||||
|
onClick={() => pageChangeHandler(totalPages - 1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages >= 7 && (
|
||||||
|
<PaginationButton
|
||||||
|
content={totalPages}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
onClick={() => pageChangeHandler(totalPages)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
|
||||||
Next{' '}
|
<div className='hidden @md:block'>
|
||||||
<Icon
|
<NextPageButton />
|
||||||
icon='uil:arrow-right'
|
</div>
|
||||||
width={20}
|
|
||||||
height={20}
|
<div className='hidden @md:block'>
|
||||||
className='text-gray-400 group-disabled:text-gray-300'
|
<GoToLastPageButton />
|
||||||
/>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
|
<div className='hidden @md:block'>
|
||||||
|
<PageInfo />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex gap-2 mt-2 sm:hidden'>
|
<div className='flex @md:hidden flex-col justify-center items-end gap-2'>
|
||||||
<button
|
<div className='flex flex-row items-center gap-0.5'>
|
||||||
disabled={currentPage === 1}
|
<GoToFirstPageButton />
|
||||||
onClick={onPrevPage}
|
<PrevPageButton />
|
||||||
className={cn(
|
<NextPageButton />
|
||||||
'join-item btn btn-outline group px-3 py-2 text-sm font-semibold rounded-lg border border-gray-300 shadow-xs flex justify-center items-center gap-1.5',
|
<GoToLastPageButton />
|
||||||
'disabled:bg-[initial]! disabled:text-gray-400 disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
</div>
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='uil:arrow-left'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
className='text-gray-400 group-disabled:text-gray-300'
|
|
||||||
/>{' '}
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<div className='flex flex-row items-center gap-4'>
|
||||||
disabled={currentPage === totalPages}
|
<DisplayedRowCountSelect />
|
||||||
onClick={onNextPage}
|
|
||||||
className={cn(
|
<PageInfo />
|
||||||
'join-item btn btn-outline group px-3 py-2 text-sm font-semibold rounded-lg border border-gray-300 shadow-xs flex justify-center items-center gap-1.5',
|
</div>
|
||||||
'disabled:bg-[initial]! disabled:text-gray-400 disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Next{' '}
|
|
||||||
<Icon
|
|
||||||
icon='uil:arrow-right'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
className='text-gray-400 group-disabled:text-gray-300'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+139
-99
@@ -14,6 +14,7 @@ import {
|
|||||||
SortingState,
|
SortingState,
|
||||||
OnChangeFn,
|
OnChangeFn,
|
||||||
Row,
|
Row,
|
||||||
|
HeaderContext,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { rankItem } from '@tanstack/match-sorter-utils';
|
import { rankItem } from '@tanstack/match-sorter-utils';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -41,6 +42,7 @@ export interface TableProps<TData extends object> {
|
|||||||
data: TData[];
|
data: TData[];
|
||||||
columns: ColumnDef<TData, unknown>[];
|
columns: ColumnDef<TData, unknown>[];
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
onPageSizeChange?: (pageSize: number) => void;
|
||||||
totalItems?: number;
|
totalItems?: number;
|
||||||
page?: number;
|
page?: number;
|
||||||
onPageChange?: (page: number) => void;
|
onPageChange?: (page: number) => void;
|
||||||
@@ -56,8 +58,8 @@ export interface TableProps<TData extends object> {
|
|||||||
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
||||||
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
||||||
renderFooter?: boolean;
|
renderFooter?: boolean;
|
||||||
footerContent?: ReactNode;
|
withCheckbox?: boolean;
|
||||||
footerData?: TData[];
|
rowOptions?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||||
@@ -70,31 +72,35 @@ const emptyContentDefaultValue = (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const TABLE_DEFAULT_STYLING = {
|
||||||
|
containerClassName: 'w-full mb-20',
|
||||||
|
tableWrapperClassName:
|
||||||
|
'overflow-x-auto border border-solid border-base-content/10 rounded-lg',
|
||||||
|
tableClassName: 'font-inter w-full table-auto text-sm font-medium',
|
||||||
|
tableHeaderClassName: '',
|
||||||
|
headerRowClassName: '',
|
||||||
|
headerColumnClassName: 'px-4 py-3 text-base-content/50',
|
||||||
|
tableBodyClassName: '',
|
||||||
|
bodyRowClassName: 'border-t border-t-base-content/10',
|
||||||
|
bodyColumnClassName: 'px-4 py-3 text-base-content',
|
||||||
|
paginationClassName: '',
|
||||||
|
tableFooterClassName: 'bg-gray-100 font-semibold border border-gray-200',
|
||||||
|
footerRowClassName: 'border-t-2 border-gray-300',
|
||||||
|
footerColumnClassName: 'px-6 py-3 text-xs text-gray-900',
|
||||||
|
};
|
||||||
|
|
||||||
const Table = <TData extends object>({
|
const Table = <TData extends object>({
|
||||||
data = [],
|
data = [],
|
||||||
columns = [],
|
columns = [],
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
|
onPageSizeChange,
|
||||||
totalItems,
|
totalItems,
|
||||||
page,
|
page,
|
||||||
onPageChange,
|
onPageChange,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
fuzzySearchValue,
|
fuzzySearchValue,
|
||||||
onFuzzySearchValueChange,
|
onFuzzySearchValueChange,
|
||||||
className = {
|
className = TABLE_DEFAULT_STYLING,
|
||||||
containerClassName: '',
|
|
||||||
tableWrapperClassName: '',
|
|
||||||
tableClassName: '',
|
|
||||||
tableHeaderClassName: '',
|
|
||||||
headerRowClassName: '',
|
|
||||||
headerColumnClassName: '',
|
|
||||||
tableBodyClassName: '',
|
|
||||||
bodyRowClassName: '',
|
|
||||||
bodyColumnClassName: '',
|
|
||||||
tableFooterClassName: '',
|
|
||||||
footerRowClassName: '',
|
|
||||||
footerColumnClassName: '',
|
|
||||||
paginationClassName: '',
|
|
||||||
},
|
|
||||||
emptyContent = emptyContentDefaultValue,
|
emptyContent = emptyContentDefaultValue,
|
||||||
sorting,
|
sorting,
|
||||||
setSorting,
|
setSorting,
|
||||||
@@ -103,14 +109,19 @@ const Table = <TData extends object>({
|
|||||||
setRowSelection,
|
setRowSelection,
|
||||||
enableRowSelection,
|
enableRowSelection,
|
||||||
renderFooter = false,
|
renderFooter = false,
|
||||||
footerContent,
|
withCheckbox = false,
|
||||||
footerData = [],
|
rowOptions = [10, 20, 50, 100],
|
||||||
}: TableProps<TData>) => {
|
}: TableProps<TData>) => {
|
||||||
const isServerSideTable =
|
const isServerSideTable =
|
||||||
totalItems !== undefined &&
|
totalItems !== undefined &&
|
||||||
page !== undefined &&
|
page !== undefined &&
|
||||||
onPageChange !== undefined;
|
onPageChange !== undefined;
|
||||||
|
|
||||||
|
const tableClassNames = {
|
||||||
|
...TABLE_DEFAULT_STYLING,
|
||||||
|
...className,
|
||||||
|
};
|
||||||
|
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
@@ -172,14 +183,6 @@ const Table = <TData extends object>({
|
|||||||
const table = useReactTable(tableOptions);
|
const table = useReactTable(tableOptions);
|
||||||
const { setPageSize } = table;
|
const { setPageSize } = table;
|
||||||
|
|
||||||
const footerTableOptions: TableOptions<TData> = {
|
|
||||||
columns,
|
|
||||||
data: footerData,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const footerTable = useReactTable(footerTableOptions);
|
|
||||||
|
|
||||||
const prevPageClickHandler = () => {
|
const prevPageClickHandler = () => {
|
||||||
table.previousPage();
|
table.previousPage();
|
||||||
|
|
||||||
@@ -211,68 +214,104 @@ const Table = <TData extends object>({
|
|||||||
}, [pageSize, setPageSize]);
|
}, [pageSize, setPageSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className.containerClassName}>
|
<div className={tableClassNames.containerClassName}>
|
||||||
<div className={className.tableWrapperClassName}>
|
<div className={tableClassNames.tableWrapperClassName}>
|
||||||
<table className={className.tableClassName}>
|
<table className={tableClassNames.tableClassName}>
|
||||||
<thead className={className.tableHeaderClassName}>
|
<thead className={tableClassNames.tableHeaderClassName}>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id} className={className.headerRowClassName}>
|
<tr
|
||||||
{headerGroup.headers.map((header) => (
|
key={headerGroup.id}
|
||||||
<th
|
className={tableClassNames.headerRowClassName}
|
||||||
key={header.id}
|
>
|
||||||
colSpan={header.colSpan}
|
{headerGroup.headers.map((header) => {
|
||||||
onClick={header.column.getToggleSortingHandler()}
|
const columnRelativeDepth =
|
||||||
className={cn(
|
header.depth - header.column.depth;
|
||||||
header.column.getCanSort()
|
if (
|
||||||
? 'cursor-pointer select-none'
|
!header.isPlaceholder &&
|
||||||
: '',
|
columnRelativeDepth > 1 &&
|
||||||
className.headerColumnClassName
|
header.id === header.column.id
|
||||||
)}
|
) {
|
||||||
>
|
return null;
|
||||||
<div className='flex items-center gap-1'>
|
}
|
||||||
{flexRender(
|
let rowSpan = 1;
|
||||||
header.column.columnDef.header,
|
if (header.isPlaceholder) {
|
||||||
header.getContext()
|
const leafs = header.getLeafHeaders();
|
||||||
|
rowSpan = leafs[leafs.length - 1].depth - header.depth;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
rowSpan={rowSpan}
|
||||||
|
onClick={header.column.getToggleSortingHandler()}
|
||||||
|
className={cn(
|
||||||
|
header.column.getCanSort()
|
||||||
|
? 'cursor-pointer select-none'
|
||||||
|
: '',
|
||||||
|
{
|
||||||
|
'first:w-9 first:pr-0': withCheckbox,
|
||||||
|
},
|
||||||
|
tableClassNames.headerColumnClassName
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn('flex items-center gap-1', {
|
||||||
|
'justify-center relative after:content-[""] after:absolute after:left-0 after:right-0 after:bottom-0 after:h-px after:bg-base-content/10 after:translate-y-4':
|
||||||
|
header.colSpan > 1,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
|
||||||
{header.column.getCanSort() && (
|
{header.column.getCanSort() && (
|
||||||
<div className='flex items-center'>
|
<div className='w-4 h-4 relative flex flex-col items-center'>
|
||||||
<Icon
|
<Icon
|
||||||
icon='lucide:arrow-up'
|
icon='heroicons:chevron-up-16-solid'
|
||||||
width={12}
|
width={18}
|
||||||
height={12}
|
height={18}
|
||||||
className={cn(
|
className={cn(
|
||||||
'transition-all ease-in-out duration-200',
|
'absolute -top-1',
|
||||||
header.column.getIsSorted() === 'asc'
|
'transition-all ease-in-out duration-200',
|
||||||
? 'text-black'
|
header.column.getIsSorted() === 'asc'
|
||||||
: 'text-black/30'
|
? 'text-black'
|
||||||
)}
|
: 'text-black/30'
|
||||||
/>
|
)}
|
||||||
<Icon
|
/>
|
||||||
icon='lucide:arrow-down'
|
<Icon
|
||||||
width={12}
|
icon='heroicons:chevron-down-16-solid'
|
||||||
height={12}
|
width={18}
|
||||||
className={cn(
|
height={18}
|
||||||
'transition-all ease-in-out duration-200',
|
className={cn(
|
||||||
header.column.getIsSorted() === 'desc'
|
'absolute -bottom-1.5',
|
||||||
? 'text-black'
|
'transition-all ease-in-out duration-200',
|
||||||
: 'text-black/30'
|
header.column.getIsSorted() === 'desc'
|
||||||
)}
|
? 'text-black'
|
||||||
/>
|
: 'text-black/30'
|
||||||
</div>
|
)}
|
||||||
)}
|
/>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
)}
|
||||||
))}
|
</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody className={className.tableBodyClassName}>
|
<tbody className={tableClassNames.tableBodyClassName}>
|
||||||
{table.getRowModel().rows.map((row) => (
|
{table.getRowModel().rows.map((row) => (
|
||||||
<tr key={row.id} className={className.bodyRowClassName}>
|
<tr key={row.id} className={tableClassNames.bodyRowClassName}>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<td key={cell.id} className={className.bodyColumnClassName}>
|
<td
|
||||||
|
key={cell.id}
|
||||||
|
className={cn(
|
||||||
|
{ 'first:w-9 first:pr-0': withCheckbox },
|
||||||
|
tableClassNames.bodyColumnClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
flexRender(cell.column.columnDef.cell, cell.getContext())}
|
flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
|
||||||
@@ -283,24 +322,23 @@ const Table = <TData extends object>({
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot className={cn(className.tableFooterClassName)}>
|
<tfoot className={cn(className.tableFooterClassName)}>
|
||||||
{renderFooter &&
|
{renderFooter && (
|
||||||
(footerData && footerData.length > 0
|
<tr className={className.footerRowClassName}>
|
||||||
? footerTable.getRowModel().rows.map((row) => (
|
{table.getAllLeafColumns().map((column) => (
|
||||||
<tr key={row.id} className={className.footerRowClassName}>
|
<td
|
||||||
{row.getVisibleCells().map((cell) => (
|
key={column.id}
|
||||||
<td
|
className={className.footerColumnClassName}
|
||||||
key={cell.id}
|
>
|
||||||
className={className.footerColumnClassName}
|
{column.columnDef.footer &&
|
||||||
>
|
flexRender(column.columnDef.footer, {
|
||||||
{flexRender(
|
column,
|
||||||
cell.column.columnDef.cell,
|
header: column.columnDef,
|
||||||
cell.getContext()
|
table,
|
||||||
)}
|
} as HeaderContext<TData, unknown>)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))
|
)}
|
||||||
: footerContent)}
|
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,7 +348,7 @@ const Table = <TData extends object>({
|
|||||||
emptyContent}
|
emptyContent}
|
||||||
|
|
||||||
{data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && (
|
{data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && (
|
||||||
<div className={cn('mt-5', className.paginationClassName)}>
|
<div className={cn('mt-5', tableClassNames.paginationClassName)}>
|
||||||
<Pagination
|
<Pagination
|
||||||
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
||||||
itemsPerPage={table.getState().pagination.pageSize}
|
itemsPerPage={table.getState().pagination.pageSize}
|
||||||
@@ -322,6 +360,8 @@ const Table = <TData extends object>({
|
|||||||
onPrevPage={prevPageClickHandler}
|
onPrevPage={prevPageClickHandler}
|
||||||
onNextPage={nextPageClickHandler}
|
onNextPage={nextPageClickHandler}
|
||||||
onPageChange={pageChangeHandler}
|
onPageChange={pageChangeHandler}
|
||||||
|
rowOptions={rowOptions}
|
||||||
|
onRowChange={onPageSizeChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ interface SalesReportTableProps {
|
|||||||
initialValues?: BaseClosingSales;
|
initialValues?: BaseClosingSales;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FooterSalesRow extends BaseSales {
|
|
||||||
_isFooter: true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SalesReportTable = ({
|
const SalesReportTable = ({
|
||||||
type = 'detail',
|
type = 'detail',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -72,29 +68,6 @@ const SalesReportTable = ({
|
|||||||
};
|
};
|
||||||
}, [salesData]);
|
}, [salesData]);
|
||||||
|
|
||||||
const footerData = useMemo((): FooterSalesRow[] => {
|
|
||||||
if (salesData.length === 0) return [];
|
|
||||||
|
|
||||||
const footerRow: FooterSalesRow = {
|
|
||||||
id: -999,
|
|
||||||
realization_date: 'Total Penjualan',
|
|
||||||
age: 0,
|
|
||||||
do_number: '',
|
|
||||||
product: {} as Product,
|
|
||||||
customer: {} as Customer,
|
|
||||||
qty: totals.totalQuantity,
|
|
||||||
weight: totals.totalWeight,
|
|
||||||
avg_weight: totals.avgWeight,
|
|
||||||
price: totals.avgPricePartner,
|
|
||||||
total_price: totals.totalPartner,
|
|
||||||
kandang: {} as Kandang,
|
|
||||||
payment_status: '',
|
|
||||||
_isFooter: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return [footerRow];
|
|
||||||
}, [salesData, totals]);
|
|
||||||
|
|
||||||
const salesColumns: ColumnDef<BaseSales>[] = useMemo(
|
const salesColumns: ColumnDef<BaseSales>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -102,43 +75,30 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'realization_date',
|
accessorKey: 'realization_date',
|
||||||
header: 'Tanggal Realisasi',
|
header: 'Tanggal Realisasi',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
|
||||||
if (isFooter) {
|
|
||||||
return (
|
|
||||||
<div className='font-semibold text-gray-900 col-span-5'>
|
|
||||||
{props.row.original.realization_date}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const date = props.row.original.realization_date;
|
const date = props.row.original.realization_date;
|
||||||
return date ? formatDate(date, 'DD MMM YYYY') : '-';
|
return date ? formatDate(date, 'DD MMM YYYY') : '-';
|
||||||
},
|
},
|
||||||
|
footer: () => (
|
||||||
|
<div className='font-semibold text-gray-900'>Total Penjualan</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'age',
|
id: 'age',
|
||||||
accessorKey: 'age',
|
accessorKey: 'age',
|
||||||
header: 'Umur',
|
header: 'Umur',
|
||||||
cell: (props) => {
|
cell: (props) => props.getValue() || '-',
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
|
||||||
return isFooter ? null : props.getValue() || '-';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'do_number',
|
id: 'do_number',
|
||||||
accessorKey: 'do_number',
|
accessorKey: 'do_number',
|
||||||
header: 'No. DO',
|
header: 'No. DO',
|
||||||
cell: (props) => {
|
cell: (props) => props.getValue() || '-',
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
|
||||||
return isFooter ? null : props.getValue() || '-';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'product',
|
id: 'product',
|
||||||
accessorKey: 'product',
|
accessorKey: 'product',
|
||||||
header: 'Produk',
|
header: 'Produk',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
|
||||||
if (isFooter) return null;
|
|
||||||
const product = props.getValue() as Product;
|
const product = props.getValue() as Product;
|
||||||
return product?.name || '-';
|
return product?.name || '-';
|
||||||
},
|
},
|
||||||
@@ -148,47 +108,43 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'customer',
|
accessorKey: 'customer',
|
||||||
header: 'Customer',
|
header: 'Customer',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
|
||||||
if (isFooter) return null;
|
|
||||||
const customer = props.getValue() as Customer;
|
const customer = props.getValue() as Customer;
|
||||||
return customer?.name || '-';
|
return customer?.name || '-';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'qty',
|
id: 'jumlah',
|
||||||
accessorKey: 'qty',
|
header: 'Jumlah',
|
||||||
header: 'Kuantitas',
|
columns: [
|
||||||
cell: (props) => {
|
{
|
||||||
const value = props.getValue() as number;
|
id: 'qty',
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
accessorKey: 'qty',
|
||||||
return (
|
header: 'Kuantitas',
|
||||||
<div
|
cell: (props) => {
|
||||||
className={
|
const value = props.getValue() as number;
|
||||||
isFooter ? 'text-left font-semibold text-gray-900' : 'text-left'
|
return <div className='text-left'>{formatNumber(value)}</div>;
|
||||||
}
|
},
|
||||||
>
|
footer: () => (
|
||||||
{formatNumber(value)}
|
<div className='text-left font-semibold text-gray-900'>
|
||||||
</div>
|
{formatNumber(totals.totalQuantity)}
|
||||||
);
|
</div>
|
||||||
},
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'weight',
|
id: 'weight',
|
||||||
accessorKey: 'weight',
|
accessorKey: 'weight',
|
||||||
header: 'Kg',
|
header: 'Kg',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
return <div className='text-left'>{formatNumber(value)}</div>;
|
||||||
return (
|
},
|
||||||
<div
|
footer: () => (
|
||||||
className={
|
<div className='text-left font-semibold text-gray-900'>
|
||||||
isFooter ? 'text-left font-semibold text-gray-900' : 'text-left'
|
{formatNumber(totals.totalWeight)}
|
||||||
}
|
</div>
|
||||||
>
|
),
|
||||||
{formatNumber(value)}
|
},
|
||||||
</div>
|
],
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'avg_weight',
|
id: 'avg_weight',
|
||||||
@@ -196,17 +152,13 @@ const SalesReportTable = ({
|
|||||||
header: 'AVG (Kg)',
|
header: 'AVG (Kg)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
return <div className='text-left'>{formatNumber(value)}</div>;
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
isFooter ? 'text-left font-semibold text-gray-900' : 'text-left'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatNumber(value)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
footer: () => (
|
||||||
|
<div className='text-left font-semibold text-gray-900'>
|
||||||
|
{formatNumber(totals.avgWeight)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'price_partner',
|
id: 'price_partner',
|
||||||
@@ -214,19 +166,13 @@ const SalesReportTable = ({
|
|||||||
header: 'Harga Mitra (Rp)',
|
header: 'Harga Mitra (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
isFooter
|
|
||||||
? 'text-right font-semibold text-gray-900'
|
|
||||||
: 'text-right'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatCurrency(value)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
footer: () => (
|
||||||
|
<div className='text-right font-semibold text-gray-900'>
|
||||||
|
{formatCurrency(totals.avgPricePartner)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'total_mitra',
|
id: 'total_mitra',
|
||||||
@@ -234,19 +180,13 @@ const SalesReportTable = ({
|
|||||||
header: 'Total Mitra (Rp)',
|
header: 'Total Mitra (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
isFooter
|
|
||||||
? 'text-right font-semibold text-gray-900'
|
|
||||||
: 'text-right'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatCurrency(value)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
footer: () => (
|
||||||
|
<div className='text-right font-semibold text-gray-900'>
|
||||||
|
{formatCurrency(totals.totalPartner)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'price_act',
|
id: 'price_act',
|
||||||
@@ -254,18 +194,7 @@ const SalesReportTable = ({
|
|||||||
header: 'Harga Act (Rp)',
|
header: 'Harga Act (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
isFooter
|
|
||||||
? 'text-right font-semibold text-gray-900'
|
|
||||||
: 'text-right'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatCurrency(value)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -274,18 +203,7 @@ const SalesReportTable = ({
|
|||||||
header: 'Total Act (Rp)',
|
header: 'Total Act (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
isFooter
|
|
||||||
? 'text-right font-semibold text-gray-900'
|
|
||||||
: 'text-right'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatCurrency(value)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -293,8 +211,6 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'kandang',
|
accessorKey: 'kandang',
|
||||||
header: 'Kandang',
|
header: 'Kandang',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
|
||||||
if (isFooter) return null;
|
|
||||||
const kandang = props.getValue() as Kandang;
|
const kandang = props.getValue() as Kandang;
|
||||||
return kandang?.name || '-';
|
return kandang?.name || '-';
|
||||||
},
|
},
|
||||||
@@ -304,9 +220,6 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'payment_status',
|
accessorKey: 'payment_status',
|
||||||
header: 'Status Pembayaran',
|
header: 'Status Pembayaran',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const isFooter = '_isFooter' in props.row.original;
|
|
||||||
if (isFooter) return null;
|
|
||||||
|
|
||||||
const status = props.getValue() as string;
|
const status = props.getValue() as string;
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
if (!status) return 'neutral';
|
if (!status) return 'neutral';
|
||||||
@@ -345,16 +258,14 @@ const SalesReportTable = ({
|
|||||||
<Table
|
<Table
|
||||||
data={salesData}
|
data={salesData}
|
||||||
columns={salesColumns}
|
columns={salesColumns}
|
||||||
footerData={footerData}
|
|
||||||
renderFooter={salesData.length > 0}
|
renderFooter={salesData.length > 0}
|
||||||
className={{
|
className={{
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
tableClassName: 'w-full table-auto text-sm',
|
tableClassName: 'w-full table-auto text-sm',
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
|
||||||
headerColumnClassName:
|
headerColumnClassName:
|
||||||
'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap',
|
'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap',
|
||||||
bodyRowClassName:
|
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',
|
'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200',
|
||||||
bodyColumnClassName:
|
bodyColumnClassName:
|
||||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||||
tableFooterClassName:
|
tableFooterClassName:
|
||||||
|
|||||||
Reference in New Issue
Block a user