mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-21 13:55:45 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d8a6ff852 |
@@ -140,6 +140,7 @@ deploy:dev:
|
|||||||
environment:
|
environment:
|
||||||
name: development
|
name: development
|
||||||
url: https://dev-lti-erp.mbugroup.id
|
url: https://dev-lti-erp.mbugroup.id
|
||||||
|
|
||||||
# ====== PRODUCTION ======
|
# ====== PRODUCTION ======
|
||||||
# build:production:
|
# build:production:
|
||||||
# <<: *build_template
|
# <<: *build_template
|
||||||
@@ -162,3 +163,4 @@ deploy:dev:
|
|||||||
# environment:
|
# environment:
|
||||||
# name: production
|
# name: production
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Generated
+13
-13
@@ -15,7 +15,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "^15.5.7",
|
"next": "15.5.7",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"daisyui": "^5.5.5",
|
"daisyui": "^5.1.12",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "^15.5.7",
|
"eslint-config-next": "15.5.3",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
@@ -1088,9 +1088,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
"version": "15.5.7",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.3.tgz",
|
||||||
"integrity": "sha512-DtRU2N7BkGr8r+pExfuWHwMEPX5SD57FeA6pxdgCHODo+b/UgIgjE+rgWKtJAbEbGhVZ2jtHn4g3wNhWFoNBQQ==",
|
"integrity": "sha512-SdhaKdko6dpsSr0DldkESItVrnPYB1NS2NpShCSX5lc7SSQmLZt5Mug6t2xbiuVWEVDLZSuIAoQyYVBYp0dR5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3063,9 +3063,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "5.5.5",
|
"version": "5.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.3.10.tgz",
|
||||||
"integrity": "sha512-ekvI93ZkWIJoCOtDl0D2QMxnWvTejk9V5nWBqRv+7t0xjiBXqAK5U6o6JE2RPvlIC3EqwNyUoIZSdHX9MZK3nw==",
|
"integrity": "sha512-vmjyPmm0hvFhA95KB6uiGmWakziB2pBv6CUcs5Ka/3iMBMn9S+C3SZYx9G9l2JrgTZ1EFn61F/HrPcwaUm2kLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -3571,13 +3571,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-config-next": {
|
"node_modules/eslint-config-next": {
|
||||||
"version": "15.5.7",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.3.tgz",
|
||||||
"integrity": "sha512-nU/TRGHHeG81NeLW5DeQT5t6BDUqbpsNQTvef1ld/tqHT+/zTx60/TIhKnmPISTTe++DVo+DLxDmk4rnwHaZVw==",
|
"integrity": "sha512-e6j+QhQFOr5pfsc8VJbuTD9xTXJaRvMHYjEeLPA2pFkheNlgPLCkxdvhxhfuM4KGcqSZj2qEnpHisdTVs3BxuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/eslint-plugin-next": "15.5.7",
|
"@next/eslint-plugin-next": "15.5.3",
|
||||||
"@rushstack/eslint-patch": "^1.10.3",
|
"@rushstack/eslint-patch": "^1.10.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||||
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
|
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||||
|
|||||||
+3
-3
@@ -18,7 +18,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "^15.5.7",
|
"next": "15.5.7",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
@@ -39,9 +39,9 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"daisyui": "^5.5.5",
|
"daisyui": "^5.1.12",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "^15.5.7",
|
"eslint-config-next": "15.5.3",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
|||||||
@@ -1,21 +1,161 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Drawer from '@/components/Drawer';
|
import Drawer from '@/components/Drawer';
|
||||||
|
import Menu from '@/components/menu/Menu';
|
||||||
|
import MenuItem from '@/components/menu/MenuItem';
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Collapse from '@/components/Collapse';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import SidebarMenu from '@/components/molecules/SidebarMenu';
|
|
||||||
|
|
||||||
import { useUiStore } from '@/stores/ui/ui.store';
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
import { MAIN_DRAWER_LINKS } from '@/config/constant';
|
import { MAIN_DRAWER_LINKS } from '@/config/constant';
|
||||||
import { isPathActive } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
|
type CollapseMenuProps = {
|
||||||
|
title: string;
|
||||||
|
link: string;
|
||||||
|
icon: string;
|
||||||
|
submenu?: CollapseMenuProps[];
|
||||||
|
depth?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPathActive = (pathname: string, link?: string) => {
|
||||||
|
if (!link) return false;
|
||||||
|
|
||||||
|
const splittedPathname = pathname.split('/');
|
||||||
|
const splittedLink = link.split('/');
|
||||||
|
|
||||||
|
const isActiveLinkValid = splittedLink.every((linkChunk, idx) => {
|
||||||
|
return linkChunk === splittedPathname[idx];
|
||||||
|
});
|
||||||
|
|
||||||
|
return pathname.startsWith(link) && isActiveLinkValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CollapseMenu = ({
|
||||||
|
title,
|
||||||
|
link,
|
||||||
|
icon,
|
||||||
|
submenu,
|
||||||
|
depth = 0,
|
||||||
|
}: CollapseMenuProps) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const isActive = isPathActive(pathname, link);
|
||||||
|
const [open, setOpen] = useState(isActive);
|
||||||
|
|
||||||
|
const menuCollapseTitle = (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'w-full px-3 py-2 rounded-md text-base font-semibold transition-colors flex flex-row justify-between items-center gap-2 hover:bg-primary/10 opacity-40',
|
||||||
|
{
|
||||||
|
'bg-primary/10 opacity-100': open || isActive,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='flex flex-row items-center gap-2'>
|
||||||
|
<Icon icon={icon} width={20} height={20} />
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
icon='cuida:caret-up-outline'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className={cn('transition-transform', {
|
||||||
|
'rotate-90': !open,
|
||||||
|
'rotate-180': open,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapse
|
||||||
|
open={open}
|
||||||
|
title={menuCollapseTitle}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
className='w-full'
|
||||||
|
titleClassName='w-full p-0!'
|
||||||
|
>
|
||||||
|
<Menu>
|
||||||
|
<div
|
||||||
|
className='w-full py-0.5 flex flex-col gap-0.5'
|
||||||
|
style={{
|
||||||
|
paddingLeft: `${0.5 * (depth + 1)}rem`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{submenu?.map((item, idx) => {
|
||||||
|
const hasSubmenu = item.submenu && item.submenu.length > 0;
|
||||||
|
|
||||||
|
if (!hasSubmenu) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={idx}
|
||||||
|
title={item.title}
|
||||||
|
href={item.link}
|
||||||
|
icon={item.icon}
|
||||||
|
active={isPathActive(pathname, item.link)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CollapseMenu
|
||||||
|
key={idx}
|
||||||
|
title={item.title}
|
||||||
|
link={item.link}
|
||||||
|
icon={item.icon}
|
||||||
|
submenu={item.submenu}
|
||||||
|
depth={depth + 1}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Menu>
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MainDrawerMenu = () => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
{MAIN_DRAWER_LINKS.map((item, idx) => {
|
||||||
|
const hasSubmenu = item.submenu && item.submenu.length > 0;
|
||||||
|
|
||||||
|
if (!hasSubmenu) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={idx}
|
||||||
|
title={item.title}
|
||||||
|
href={item.link}
|
||||||
|
icon={item.icon}
|
||||||
|
active={pathname.startsWith(item.link)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CollapseMenu
|
||||||
|
key={idx}
|
||||||
|
title={item.title}
|
||||||
|
link={item.link}
|
||||||
|
icon={item.icon}
|
||||||
|
submenu={item.submenu}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const MainDrawerContent = () => {
|
const MainDrawerContent = () => {
|
||||||
const pathname = usePathname();
|
|
||||||
const { setMainDrawerOpen } = useUiStore();
|
const { setMainDrawerOpen } = useUiStore();
|
||||||
|
|
||||||
const closeMainDrawerHandler = () => {
|
const closeMainDrawerHandler = () => {
|
||||||
@@ -51,7 +191,7 @@ const MainDrawerContent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SidebarMenu menu={MAIN_DRAWER_LINKS} activeLink={pathname} />
|
<MainDrawerMenu />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -76,9 +216,9 @@ const MainDrawer = ({
|
|||||||
const hasSubmenu = menu?.submenu && menu?.submenu.length > 0;
|
const hasSubmenu = menu?.submenu && menu?.submenu.length > 0;
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
title += menu?.text;
|
title += menu?.title;
|
||||||
} else {
|
} else {
|
||||||
title += ' - ' + menu?.text;
|
title += ' - ' + menu?.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasSubmenu || !menu.submenu) return;
|
if (!hasSubmenu || !menu.submenu) return;
|
||||||
|
|||||||
+208
-298
@@ -1,9 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ChangeEventHandler, ReactNode } from 'react';
|
import { 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';
|
||||||
|
|
||||||
@@ -19,18 +17,16 @@ const PaginationButton = ({
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}) => (
|
}) => (
|
||||||
<Button
|
<button
|
||||||
variant='ghost'
|
className={cn(
|
||||||
color='none'
|
'join-item btn btn-ghost p-2.5 rounded-lg text-sm font-medium text-gray-500 aspect-square',
|
||||||
|
'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 = ({
|
||||||
@@ -94,20 +90,16 @@ 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
|
||||||
@@ -115,139 +107,30 @@ 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 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 className='@container'>
|
<div>
|
||||||
<div className='flex flex-row justify-center items-center'>
|
<div className='join w-full justify-between items-center gap-3'>
|
||||||
<div className='hidden @lg:block'>
|
<button
|
||||||
<DisplayedRowCountSelect />
|
disabled={currentPage === 1}
|
||||||
</div>
|
onClick={onPrevPage}
|
||||||
|
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>
|
||||||
|
|
||||||
<div className='join w-full justify-end @lg:justify-center items-center gap-0.5'>
|
{totalPages <= 7 && (
|
||||||
<div className='hidden @lg:block'>
|
<div className='join-item join gap-0.5'>
|
||||||
<GoToFirstPageButton />
|
{range(1, totalPages).map((pageNumber) => (
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='hidden @lg:block'>
|
|
||||||
<PrevPageButton />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{totalPages <= 7 &&
|
|
||||||
range(1, totalPages).map((pageNumber) => (
|
|
||||||
<PaginationButton
|
<PaginationButton
|
||||||
key={pageNumber}
|
key={pageNumber}
|
||||||
content={pageNumber}
|
content={pageNumber}
|
||||||
@@ -255,168 +138,195 @@ const Pagination = ({
|
|||||||
onClick={() => pageChangeHandler(pageNumber)}
|
onClick={() => pageChangeHandler(pageNumber)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{totalPages > 7 && (
|
{totalPages > 7 && (
|
||||||
<>
|
<div className='join-item join gap-0.5'>
|
||||||
<PaginationButton
|
<PaginationButton
|
||||||
content={1}
|
content={1}
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
onClick={() => pageChangeHandler(1)}
|
onClick={() => pageChangeHandler(1)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{totalPages >= 2 &&
|
{totalPages >= 2 &&
|
||||||
(currentPage <= 3 || currentPage >= 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={2}
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === 2}
|
||||||
onClick={() => pageChangeHandler(totalPages)}
|
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)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
onClick={onNextPage}
|
||||||
|
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'
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
<div className='hidden @lg:block'>
|
Next{' '}
|
||||||
<NextPageButton />
|
<Icon
|
||||||
</div>
|
icon='uil:arrow-right'
|
||||||
|
width={20}
|
||||||
<div className='hidden @lg:block'>
|
height={20}
|
||||||
<GoToLastPageButton />
|
className='text-gray-400 group-disabled:text-gray-300'
|
||||||
</div>
|
/>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<div className='hidden @lg:block'>
|
|
||||||
<PageInfo />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex @lg:hidden flex-col justify-center items-end gap-2'>
|
<div className='flex gap-2 mt-2 sm:hidden'>
|
||||||
<div className='flex flex-row items-center gap-0.5'>
|
<button
|
||||||
<GoToFirstPageButton />
|
disabled={currentPage === 1}
|
||||||
<PrevPageButton />
|
onClick={onPrevPage}
|
||||||
<NextPageButton />
|
className={cn(
|
||||||
<GoToLastPageButton />
|
'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'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='uil:arrow-left'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className='text-gray-400 group-disabled:text-gray-300'
|
||||||
|
/>{' '}
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
|
||||||
<div className='flex flex-row items-center gap-4'>
|
<button
|
||||||
<DisplayedRowCountSelect />
|
disabled={currentPage === totalPages}
|
||||||
|
onClick={onNextPage}
|
||||||
<PageInfo />
|
className={cn(
|
||||||
</div>
|
'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',
|
||||||
|
'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>
|
||||||
);
|
);
|
||||||
|
|||||||
+29
-59
@@ -38,7 +38,6 @@ 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;
|
||||||
@@ -53,8 +52,6 @@ export interface TableProps<TData extends object> {
|
|||||||
rowSelection?: Record<string, boolean>;
|
rowSelection?: Record<string, boolean>;
|
||||||
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
||||||
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
||||||
withCheckbox?: boolean;
|
|
||||||
rowOptions?: number[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||||
@@ -67,32 +64,28 @@ 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: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = TABLE_DEFAULT_STYLING,
|
className = {
|
||||||
|
containerClassName: '',
|
||||||
|
tableWrapperClassName: '',
|
||||||
|
tableClassName: '',
|
||||||
|
tableHeaderClassName: '',
|
||||||
|
headerRowClassName: '',
|
||||||
|
headerColumnClassName: '',
|
||||||
|
tableBodyClassName: '',
|
||||||
|
bodyRowClassName: '',
|
||||||
|
bodyColumnClassName: '',
|
||||||
|
paginationClassName: '',
|
||||||
|
},
|
||||||
emptyContent = emptyContentDefaultValue,
|
emptyContent = emptyContentDefaultValue,
|
||||||
sorting,
|
sorting,
|
||||||
setSorting,
|
setSorting,
|
||||||
@@ -100,19 +93,12 @@ const Table = <TData extends object>({
|
|||||||
rowSelection,
|
rowSelection,
|
||||||
setRowSelection,
|
setRowSelection,
|
||||||
enableRowSelection,
|
enableRowSelection,
|
||||||
withCheckbox = false,
|
|
||||||
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,
|
||||||
@@ -205,15 +191,12 @@ const Table = <TData extends object>({
|
|||||||
}, [pageSize, setPageSize]);
|
}, [pageSize, setPageSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={tableClassNames.containerClassName}>
|
<div className={className.containerClassName}>
|
||||||
<div className={tableClassNames.tableWrapperClassName}>
|
<div className={className.tableWrapperClassName}>
|
||||||
<table className={tableClassNames.tableClassName}>
|
<table className={className.tableClassName}>
|
||||||
<thead className={tableClassNames.tableHeaderClassName}>
|
<thead className={className.tableHeaderClassName}>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr
|
<tr key={headerGroup.id} className={className.headerRowClassName}>
|
||||||
key={headerGroup.id}
|
|
||||||
className={tableClassNames.headerRowClassName}
|
|
||||||
>
|
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
<th
|
<th
|
||||||
key={header.id}
|
key={header.id}
|
||||||
@@ -223,10 +206,7 @@ const Table = <TData extends object>({
|
|||||||
header.column.getCanSort()
|
header.column.getCanSort()
|
||||||
? 'cursor-pointer select-none'
|
? 'cursor-pointer select-none'
|
||||||
: '',
|
: '',
|
||||||
{
|
className.headerColumnClassName
|
||||||
'first:w-9 first:pr-0': withCheckbox,
|
|
||||||
},
|
|
||||||
tableClassNames.headerColumnClassName
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
@@ -236,13 +216,12 @@ const Table = <TData extends object>({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{header.column.getCanSort() && (
|
{header.column.getCanSort() && (
|
||||||
<div className='w-4 h-4 relative flex flex-col items-center'>
|
<div className='flex items-center'>
|
||||||
<Icon
|
<Icon
|
||||||
icon='heroicons:chevron-up-16-solid'
|
icon='lucide:arrow-up'
|
||||||
width={18}
|
width={12}
|
||||||
height={18}
|
height={12}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute -top-1',
|
|
||||||
'transition-all ease-in-out duration-200',
|
'transition-all ease-in-out duration-200',
|
||||||
header.column.getIsSorted() === 'asc'
|
header.column.getIsSorted() === 'asc'
|
||||||
? 'text-black'
|
? 'text-black'
|
||||||
@@ -250,11 +229,10 @@ const Table = <TData extends object>({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
icon='heroicons:chevron-down-16-solid'
|
icon='lucide:arrow-down'
|
||||||
width={18}
|
width={12}
|
||||||
height={18}
|
height={12}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute -bottom-1.5',
|
|
||||||
'transition-all ease-in-out duration-200',
|
'transition-all ease-in-out duration-200',
|
||||||
header.column.getIsSorted() === 'desc'
|
header.column.getIsSorted() === 'desc'
|
||||||
? 'text-black'
|
? 'text-black'
|
||||||
@@ -270,17 +248,11 @@ const Table = <TData extends object>({
|
|||||||
))}
|
))}
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody className={tableClassNames.tableBodyClassName}>
|
<tbody className={className.tableBodyClassName}>
|
||||||
{table.getRowModel().rows.map((row) => (
|
{table.getRowModel().rows.map((row) => (
|
||||||
<tr key={row.id} className={tableClassNames.bodyRowClassName}>
|
<tr key={row.id} className={className.bodyRowClassName}>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<td
|
<td key={cell.id} className={className.bodyColumnClassName}>
|
||||||
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())}
|
||||||
|
|
||||||
@@ -298,7 +270,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', tableClassNames.paginationClassName)}>
|
<div className={cn('mt-5', className.paginationClassName)}>
|
||||||
<Pagination
|
<Pagination
|
||||||
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
||||||
itemsPerPage={table.getState().pagination.pageSize}
|
itemsPerPage={table.getState().pagination.pageSize}
|
||||||
@@ -310,8 +282,6 @@ const Table = <TData extends object>({
|
|||||||
onPrevPage={prevPageClickHandler}
|
onPrevPage={prevPageClickHandler}
|
||||||
onNextPage={nextPageClickHandler}
|
onNextPage={nextPageClickHandler}
|
||||||
onPageChange={pageChangeHandler}
|
onPageChange={pageChangeHandler}
|
||||||
rowOptions={rowOptions}
|
|
||||||
onRowChange={onPageSizeChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
import { HTMLProps, useEffect, useRef } from 'react';
|
import { HTMLProps, useEffect, useRef } from 'react';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { Size } from '@/types/theme';
|
|
||||||
|
|
||||||
interface CheckboxInputProps extends Omit<HTMLProps<HTMLInputElement>, 'size'> {
|
interface CheckboxInputProps extends HTMLProps<HTMLInputElement> {
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
@@ -17,7 +16,6 @@ interface CheckboxInputProps extends Omit<HTMLProps<HTMLInputElement>, 'size'> {
|
|||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
isValid?: boolean;
|
isValid?: boolean;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
size?: Size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckboxInput = ({
|
const CheckboxInput = ({
|
||||||
@@ -29,19 +27,10 @@ const CheckboxInput = ({
|
|||||||
isValid,
|
isValid,
|
||||||
isError,
|
isError,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
size = 'sm',
|
|
||||||
...rest
|
...rest
|
||||||
}: CheckboxInputProps) => {
|
}: CheckboxInputProps) => {
|
||||||
const ref = useRef<HTMLInputElement>(null!);
|
const ref = useRef<HTMLInputElement>(null!);
|
||||||
|
|
||||||
const checkboxBaseClassName = cn('checkbox cursor-pointer rounded-md', {
|
|
||||||
'checkbox-xs': size === 'xs',
|
|
||||||
'checkbox-sm': size === 'sm',
|
|
||||||
'checkbox-md': size === 'md',
|
|
||||||
'checkbox-lg': size === 'lg',
|
|
||||||
'checkbox-xl': size === 'xl',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof indeterminate === 'boolean') {
|
if (typeof indeterminate === 'boolean') {
|
||||||
ref.current.indeterminate = !rest.checked && indeterminate;
|
ref.current.indeterminate = !rest.checked && indeterminate;
|
||||||
@@ -64,7 +53,7 @@ const CheckboxInput = ({
|
|||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
className={cn(
|
className={cn(
|
||||||
checkboxBaseClassName,
|
'checkbox cursor-pointer',
|
||||||
{
|
{
|
||||||
'border-error': isError,
|
'border-error': isError,
|
||||||
'border-success': isValid,
|
'border-success': isValid,
|
||||||
|
|||||||
@@ -1,32 +1,16 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { Size } from '@/types/theme';
|
|
||||||
|
|
||||||
interface MenuProps {
|
interface MenuProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
size?: Size;
|
|
||||||
direction?: 'vertical' | 'horizontal';
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Menu = ({
|
const Menu = ({ children, className }: MenuProps) => {
|
||||||
children,
|
return (
|
||||||
size = 'md',
|
<ul className={cn('menu w-full p-0 gap-0.5', className)}>{children}</ul>
|
||||||
direction = 'vertical',
|
);
|
||||||
className,
|
|
||||||
}: MenuProps) => {
|
|
||||||
const menuBaseClassName = cn('menu w-full', {
|
|
||||||
'menu-xs': size === 'xs',
|
|
||||||
'menu-sm': size === 'sm',
|
|
||||||
'menu-md': size === 'md',
|
|
||||||
'menu-lg': size === 'lg',
|
|
||||||
'menu-xl': size === 'xl',
|
|
||||||
'menu-vertical': direction === 'vertical',
|
|
||||||
'menu-horizontal': direction === 'horizontal',
|
|
||||||
});
|
|
||||||
|
|
||||||
return <ul className={cn(menuBaseClassName, className)}>{children}</ul>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Menu;
|
export default Menu;
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
import Menu from '@/components/menu/Menu';
|
|
||||||
import { Icon } from '@iconify/react';
|
|
||||||
import { cn, isPathActive } from '@/lib/helper';
|
|
||||||
|
|
||||||
export interface SidebarMenuItem {
|
|
||||||
type?: 'item' | 'title';
|
|
||||||
text: string;
|
|
||||||
link: string;
|
|
||||||
icon?: string;
|
|
||||||
submenu?: SidebarMenuItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SidebarMenuItemProps {
|
|
||||||
item: SidebarMenuItem;
|
|
||||||
activeLink: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SidebarMenuProps {
|
|
||||||
menu: SidebarMenuItem[];
|
|
||||||
activeLink: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SidebarMenuItem = ({ item, activeLink }: SidebarMenuItemProps) => {
|
|
||||||
const isItemActive = isPathActive(activeLink, item.link);
|
|
||||||
|
|
||||||
const menuItemWithoutSubmenu = (
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
href={item.link}
|
|
||||||
className={cn(
|
|
||||||
{
|
|
||||||
'menu-active border-2 border-solid border-base-300': isItemActive,
|
|
||||||
},
|
|
||||||
'px-3 py-1.5'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{item.icon && <Icon icon={item.icon} width={20} height={20} />}
|
|
||||||
|
|
||||||
<span className='text-base'>{item.text}</span>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!item.submenu || item.submenu.length === 0) {
|
|
||||||
return menuItemWithoutSubmenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
const menuItemWithSubmenu = (
|
|
||||||
<li>
|
|
||||||
<details open={isItemActive}>
|
|
||||||
<summary
|
|
||||||
className={cn({
|
|
||||||
'text-primary': isItemActive,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{item.icon && <Icon icon={item.icon} width={20} height={20} />}
|
|
||||||
|
|
||||||
<span className='text-base'>{item.text}</span>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{item.submenu.map((submenuItem, submenuIdx) => (
|
|
||||||
<SidebarMenuItem
|
|
||||||
key={`submenu#${submenuIdx}`}
|
|
||||||
item={submenuItem}
|
|
||||||
activeLink={activeLink}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
|
|
||||||
return menuItemWithSubmenu;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SidebarMenu = ({ menu, activeLink }: SidebarMenuProps) => {
|
|
||||||
return (
|
|
||||||
<Menu>
|
|
||||||
{menu.map((menuItem, menuIdx) => (
|
|
||||||
<SidebarMenuItem
|
|
||||||
key={menuIdx}
|
|
||||||
item={menuItem}
|
|
||||||
activeLink={activeLink}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SidebarMenu;
|
|
||||||
+73
-34
@@ -1,116 +1,155 @@
|
|||||||
import { SidebarMenuItem } from '@/components/molecules/SidebarMenu';
|
type MAIN_DRAWER_MENU = {
|
||||||
|
title: string;
|
||||||
|
link: string;
|
||||||
|
icon: string;
|
||||||
|
submenu?: MAIN_DRAWER_MENU[];
|
||||||
|
};
|
||||||
|
|
||||||
export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [
|
||||||
{
|
{
|
||||||
text: 'Dashboard',
|
title: 'Dashboard',
|
||||||
link: '/dashboard',
|
link: '/dashboard',
|
||||||
icon: 'heroicons-outline:chart-bar-square',
|
icon: 'gg:chart',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: 'Produksi',
|
title: 'Produksi',
|
||||||
link: '/production',
|
link: '/production',
|
||||||
icon: 'heroicons-outline:wrench-screwdriver',
|
icon: 'material-symbols:conveyor-belt-outline-rounded',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'Daftar Flock',
|
title: 'List Flock',
|
||||||
link: '/production/project-flock',
|
link: '/production/project-flock',
|
||||||
|
icon: 'material-symbols:list-alt-add-outline-rounded',
|
||||||
},
|
},
|
||||||
|
// { // DI HILANGKAN PADA VERSI REFACTORING
|
||||||
|
// title: 'Chick In',
|
||||||
|
// link: '/production/chickin',
|
||||||
|
// icon: 'mdi:home-import-outline',
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
text: 'Recording',
|
title: 'Recording',
|
||||||
link: '/production/recording',
|
link: '/production/recording',
|
||||||
|
icon: 'mdi:clipboard-text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Transfer to Laying',
|
title: 'Transfer ke Laying',
|
||||||
link: '/production/transfer-to-laying',
|
link: '/production/transfer-to-laying',
|
||||||
|
icon: 'streamline:transfer-van',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: 'Pembelian',
|
title: 'Pembelian',
|
||||||
link: '/purchase',
|
link: '/purchase',
|
||||||
icon: 'heroicons-outline:shopping-cart',
|
icon: 'gg:shopping-cart',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: 'Penjualan',
|
title: 'Penjualan',
|
||||||
link: '/marketing',
|
link: '/marketing',
|
||||||
icon: 'heroicons-outline:currency-dollar',
|
icon: 'mdi:attach-money',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: 'Biaya Operasional',
|
title: 'Biaya Operasional',
|
||||||
link: '/expense',
|
link: '/expense',
|
||||||
icon: 'heroicons:wallet',
|
icon: 'uil:wallet',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: 'Persediaan',
|
title: 'Persediaan',
|
||||||
link: '/inventory',
|
link: '/inventory',
|
||||||
icon: 'heroicons-outline:folder',
|
icon: 'mdi:warehouse',
|
||||||
submenu: [
|
submenu: [
|
||||||
|
// {
|
||||||
|
// title: 'Product',
|
||||||
|
// link: '/inventory/product',
|
||||||
|
// icon: 'mdi:package-variant-closed',
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
text: 'Penyesuaian Stok',
|
title: 'Penyesuaian Stok',
|
||||||
link: '/inventory/adjustment',
|
link: '/inventory/adjustment',
|
||||||
|
icon: 'mdi:database-edit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Transfer Stok',
|
title: 'Transfer Stok',
|
||||||
link: '/inventory/movement',
|
link: '/inventory/movement',
|
||||||
|
icon: 'mdi:swap-horizontal',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: 'Master Data',
|
title: 'Master Data',
|
||||||
link: '/master-data',
|
link: '/master-data',
|
||||||
icon: 'heroicons-outline:circle-stack',
|
icon: 'majesticons:data-line',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'Produk',
|
title: 'Product',
|
||||||
link: '/master-data/product',
|
link: '/master-data/product',
|
||||||
|
icon: 'fluent-mdl2:product-variant',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Kategori Produk',
|
title: 'Product Category',
|
||||||
link: '/master-data/product-category',
|
link: '/master-data/product-category',
|
||||||
|
icon: 'carbon:categories',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Bank',
|
title: 'Bank',
|
||||||
link: '/master-data/bank',
|
link: '/master-data/bank',
|
||||||
|
icon: 'mdi:bank-outline',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Area',
|
title: 'Area',
|
||||||
link: '/master-data/area',
|
link: '/master-data/area',
|
||||||
|
icon: 'majesticons:map-marker-area-line',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lokasi',
|
title: 'Location',
|
||||||
link: '/master-data/location',
|
link: '/master-data/location',
|
||||||
|
icon: 'mingcute:location-line',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Kandang',
|
title: 'Kandang',
|
||||||
link: '/master-data/kandang',
|
link: '/master-data/kandang',
|
||||||
|
icon: 'mdi:farm-home-outline',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Warehouse',
|
title: 'Warehouse',
|
||||||
link: '/master-data/warehouse',
|
link: '/master-data/warehouse',
|
||||||
|
icon: 'hugeicons:warehouse',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Customer',
|
title: 'Customer',
|
||||||
link: '/master-data/customer',
|
link: '/master-data/customer',
|
||||||
|
icon: 'ix:customer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'UOM',
|
title: 'UOM',
|
||||||
link: '/master-data/uom',
|
link: '/master-data/uom',
|
||||||
|
icon: 'lsicon:measure-outline',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Non-Stock',
|
title: 'Non-Stock',
|
||||||
link: '/master-data/nonstock',
|
link: '/master-data/nonstock',
|
||||||
|
icon: 'fluent:box-32-regular',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'FCR',
|
title: 'FCR',
|
||||||
link: '/master-data/fcr',
|
link: '/master-data/fcr',
|
||||||
|
icon: 'fluent:food-chicken-leg-16-regular',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Supplier',
|
title: 'Supplier',
|
||||||
link: '/master-data/supplier',
|
link: '/master-data/supplier',
|
||||||
|
icon: 'material-symbols:add-business-outline-rounded',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Flock',
|
title: 'Flock',
|
||||||
link: '/master-data/flock',
|
link: '/master-data/flock',
|
||||||
|
icon: 'material-symbols:raven-outline-rounded',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -119,16 +119,3 @@ export const convertRowSelectionObjToArr = (
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isPathActive = (pathname: string, link?: string) => {
|
|
||||||
if (!link) return false;
|
|
||||||
|
|
||||||
const splittedPathname = pathname.split('/');
|
|
||||||
const splittedLink = link.split('/');
|
|
||||||
|
|
||||||
const isActiveLinkValid = splittedLink.every((linkChunk, idx) => {
|
|
||||||
return linkChunk === splittedPathname[idx];
|
|
||||||
});
|
|
||||||
|
|
||||||
return pathname.startsWith(link) && isActiveLinkValid;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
@layer utilities {
|
@layer utilities {
|
||||||
.menu {
|
|
||||||
--menu-active-fg: var(--color-primary);
|
|
||||||
--menu-active-bg: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step.step-success::before {
|
.step.step-success::before {
|
||||||
--step-bg: var(--color-success);
|
--step-bg: var(--color-success);
|
||||||
--step-fg: var(--color-success-content);
|
--step-fg: var(--color-success-content);
|
||||||
|
|||||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
export type Color =
|
type Color =
|
||||||
| 'primary'
|
| 'primary'
|
||||||
| 'secondary'
|
| 'secondary'
|
||||||
| 'accent'
|
| 'accent'
|
||||||
@@ -9,4 +9,4 @@ export type Color =
|
|||||||
| 'error'
|
| 'error'
|
||||||
| 'none';
|
| 'none';
|
||||||
|
|
||||||
export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
export { Color };
|
||||||
|
|||||||
Reference in New Issue
Block a user