mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bac3f30ce3 | |||
| be725d42c3 | |||
| b37c3f87b0 | |||
| ae4c17b391 | |||
| 48dd6d7218 | |||
| cee3d4ba90 | |||
| a8d7fdc30d | |||
| 2bb2da74e6 | |||
| fd024fdb8f | |||
| 79a89ea193 | |||
| 611655e408 | |||
| 702943c55c | |||
| 58532881f4 | |||
| 4073f4dfde | |||
| 94e2d71dba | |||
| 944f479e2d | |||
| 5046d687b5 | |||
| 711deda6a8 |
+21
-3
@@ -15,8 +15,24 @@ stages:
|
||||
script:
|
||||
- echo "Installing dependencies..."
|
||||
- npm ci --no-audit --no-fund
|
||||
- echo "Build env used:"
|
||||
- echo "NEXT_PUBLIC_LTI_URL=$NEXT_PUBLIC_LTI_URL"
|
||||
- echo "NEXT_PUBLIC_SSO_LOGIN_URL=$NEXT_PUBLIC_SSO_LOGIN_URL"
|
||||
- echo "NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL"
|
||||
- echo "Building Next.js static export..."
|
||||
- npx next build
|
||||
- |
|
||||
mkdir -p out
|
||||
cat <<EOF > out/build-info.json
|
||||
{
|
||||
"commit": "$CI_COMMIT_SHORT_SHA",
|
||||
"pipeline": "$CI_PIPELINE_ID",
|
||||
"built_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||||
"NEXT_PUBLIC_LTI_URL": "$NEXT_PUBLIC_LTI_URL",
|
||||
"NEXT_PUBLIC_SSO_LOGIN_URL": "$NEXT_PUBLIC_SSO_LOGIN_URL",
|
||||
"NEXT_PUBLIC_API_BASE_URL": "$NEXT_PUBLIC_API_BASE_URL"
|
||||
}
|
||||
EOF
|
||||
artifacts:
|
||||
name: 'out-$CI_COMMIT_SHORT_SHA'
|
||||
paths:
|
||||
@@ -106,8 +122,11 @@ build:dev:
|
||||
environment:
|
||||
name: development
|
||||
variables:
|
||||
NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id'
|
||||
NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-api-sso.mbugroup.id'
|
||||
# NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id'
|
||||
# NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-api-sso.mbugroup.id'
|
||||
NEXT_PUBLIC_LTI_URL: 'https://dev-lti-erp.mbugroup.id'
|
||||
NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-auth-erp.mbugroup.id'
|
||||
NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id/api'
|
||||
|
||||
deploy:dev:
|
||||
<<: *deploy_template
|
||||
@@ -142,5 +161,4 @@ deploy:dev:
|
||||
# CLOUDFRONT_DISTRIBUTION_ID: "ddfd"
|
||||
# environment:
|
||||
# name: production
|
||||
# url: https://royalgoldcapital.com
|
||||
|
||||
|
||||
Generated
+52
-52
@@ -15,7 +15,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"formik": "^2.4.6",
|
||||
"moment": "^2.30.1",
|
||||
"next": "15.5.3",
|
||||
"next": "^15.5.7",
|
||||
"react": "19.1.0",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dom": "19.1.0",
|
||||
@@ -36,9 +36,9 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"daisyui": "^5.1.12",
|
||||
"daisyui": "^5.5.5",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.5.3",
|
||||
"eslint-config-next": "^15.5.7",
|
||||
"husky": "^9.1.7",
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^4",
|
||||
@@ -1082,15 +1082,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.3.tgz",
|
||||
"integrity": "sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
|
||||
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.3.tgz",
|
||||
"integrity": "sha512-SdhaKdko6dpsSr0DldkESItVrnPYB1NS2NpShCSX5lc7SSQmLZt5Mug6t2xbiuVWEVDLZSuIAoQyYVBYp0dR5g==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.7.tgz",
|
||||
"integrity": "sha512-DtRU2N7BkGr8r+pExfuWHwMEPX5SD57FeA6pxdgCHODo+b/UgIgjE+rgWKtJAbEbGhVZ2jtHn4g3wNhWFoNBQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1098,9 +1098,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.3.tgz",
|
||||
"integrity": "sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
||||
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1114,9 +1114,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.3.tgz",
|
||||
"integrity": "sha512-w83w4SkOOhekJOcA5HBvHyGzgV1W/XvOfpkrxIse4uPWhYTTRwtGEM4v/jiXwNSJvfRvah0H8/uTLBKRXlef8g==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
||||
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1130,9 +1130,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.3.tgz",
|
||||
"integrity": "sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
||||
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1146,9 +1146,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.3.tgz",
|
||||
"integrity": "sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1162,9 +1162,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.3.tgz",
|
||||
"integrity": "sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz",
|
||||
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1178,9 +1178,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.3.tgz",
|
||||
"integrity": "sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1194,9 +1194,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.3.tgz",
|
||||
"integrity": "sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1210,9 +1210,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.3.tgz",
|
||||
"integrity": "sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3063,9 +3063,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "5.3.10",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.3.10.tgz",
|
||||
"integrity": "sha512-vmjyPmm0hvFhA95KB6uiGmWakziB2pBv6CUcs5Ka/3iMBMn9S+C3SZYx9G9l2JrgTZ1EFn61F/HrPcwaUm2kLQ==",
|
||||
"version": "5.5.5",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.5.tgz",
|
||||
"integrity": "sha512-ekvI93ZkWIJoCOtDl0D2QMxnWvTejk9V5nWBqRv+7t0xjiBXqAK5U6o6JE2RPvlIC3EqwNyUoIZSdHX9MZK3nw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -3571,13 +3571,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-next": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.3.tgz",
|
||||
"integrity": "sha512-e6j+QhQFOr5pfsc8VJbuTD9xTXJaRvMHYjEeLPA2pFkheNlgPLCkxdvhxhfuM4KGcqSZj2qEnpHisdTVs3BxuQ==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.7.tgz",
|
||||
"integrity": "sha512-nU/TRGHHeG81NeLW5DeQT5t6BDUqbpsNQTvef1ld/tqHT+/zTx60/TIhKnmPISTTe++DVo+DLxDmk4rnwHaZVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "15.5.3",
|
||||
"@next/eslint-plugin-next": "15.5.7",
|
||||
"@rushstack/eslint-patch": "^1.10.3",
|
||||
"@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",
|
||||
@@ -5654,12 +5654,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.3.tgz",
|
||||
"integrity": "sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
|
||||
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.3",
|
||||
"@next/env": "15.5.7",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
@@ -5672,14 +5672,14 @@
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.5.3",
|
||||
"@next/swc-darwin-x64": "15.5.3",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.3",
|
||||
"@next/swc-linux-arm64-musl": "15.5.3",
|
||||
"@next/swc-linux-x64-gnu": "15.5.3",
|
||||
"@next/swc-linux-x64-musl": "15.5.3",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.3",
|
||||
"@next/swc-win32-x64-msvc": "15.5.3",
|
||||
"@next/swc-darwin-arm64": "15.5.7",
|
||||
"@next/swc-darwin-x64": "15.5.7",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.7",
|
||||
"@next/swc-linux-arm64-musl": "15.5.7",
|
||||
"@next/swc-linux-x64-gnu": "15.5.7",
|
||||
"@next/swc-linux-x64-musl": "15.5.7",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.7",
|
||||
"@next/swc-win32-x64-msvc": "15.5.7",
|
||||
"sharp": "^0.34.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"formik": "^2.4.6",
|
||||
"moment": "^2.30.1",
|
||||
"next": "15.5.3",
|
||||
"next": "^15.5.7",
|
||||
"react": "19.1.0",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dom": "19.1.0",
|
||||
@@ -39,9 +39,9 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"daisyui": "^5.1.12",
|
||||
"daisyui": "^5.5.5",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.5.3",
|
||||
"eslint-config-next": "^15.5.7",
|
||||
"husky": "^9.1.7",
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^4",
|
||||
|
||||
@@ -1,161 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Drawer from '@/components/Drawer';
|
||||
import Menu from '@/components/menu/Menu';
|
||||
import MenuItem from '@/components/menu/MenuItem';
|
||||
import Navbar from '@/components/Navbar';
|
||||
import Collapse from '@/components/Collapse';
|
||||
import Button from '@/components/Button';
|
||||
import SidebarMenu from '@/components/molecules/SidebarMenu';
|
||||
|
||||
import { useUiStore } from '@/stores/ui/ui.store';
|
||||
import { MAIN_DRAWER_LINKS } from '@/config/constant';
|
||||
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>
|
||||
);
|
||||
};
|
||||
import { isPathActive } from '@/lib/helper';
|
||||
|
||||
const MainDrawerContent = () => {
|
||||
const pathname = usePathname();
|
||||
const { setMainDrawerOpen } = useUiStore();
|
||||
|
||||
const closeMainDrawerHandler = () => {
|
||||
@@ -191,7 +51,7 @@ const MainDrawerContent = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MainDrawerMenu />
|
||||
<SidebarMenu menu={MAIN_DRAWER_LINKS} activeLink={pathname} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -216,9 +76,9 @@ const MainDrawer = ({
|
||||
const hasSubmenu = menu?.submenu && menu?.submenu.length > 0;
|
||||
|
||||
if (!title) {
|
||||
title += menu?.title;
|
||||
title += menu?.text;
|
||||
} else {
|
||||
title += ' - ' + menu?.title;
|
||||
title += ' - ' + menu?.text;
|
||||
}
|
||||
|
||||
if (!hasSubmenu || !menu.submenu) return;
|
||||
|
||||
+299
-209
@@ -1,7 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { ChangeEventHandler, ReactNode } from 'react';
|
||||
|
||||
import { Icon } from '@iconify/react';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
import { cn } from '@/lib/helper';
|
||||
|
||||
@@ -17,16 +19,18 @@ const PaginationButton = ({
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
}) => (
|
||||
<button
|
||||
className={cn(
|
||||
'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'
|
||||
)}
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
disabled={disabled}
|
||||
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}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
|
||||
const EtcPaginationButton = ({
|
||||
@@ -90,16 +94,20 @@ const Pagination = ({
|
||||
currentPage = 1,
|
||||
totalItems = 0,
|
||||
itemsPerPage = 10,
|
||||
rowOptions = [10, 20, 50, 100],
|
||||
onPageChange,
|
||||
onPrevPage = () => {},
|
||||
onNextPage = () => {},
|
||||
onRowChange,
|
||||
}: {
|
||||
currentPage: number;
|
||||
totalItems: number;
|
||||
itemsPerPage: number;
|
||||
rowOptions?: number[];
|
||||
onPageChange: (pageNumber: number) => void;
|
||||
onPrevPage: () => void;
|
||||
onNextPage: () => void;
|
||||
onRowChange?: (row: number) => void;
|
||||
}) => {
|
||||
const totalPages =
|
||||
Math.ceil(totalItems / itemsPerPage) === 0
|
||||
@@ -107,30 +115,139 @@ const Pagination = ({
|
||||
: Math.ceil(totalItems / itemsPerPage);
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<div className='join w-full justify-between items-center gap-3'>
|
||||
<button
|
||||
disabled={currentPage === 1}
|
||||
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='@container'>
|
||||
<div className='flex flex-row justify-center items-center'>
|
||||
<div className='hidden @lg:block'>
|
||||
<DisplayedRowCountSelect />
|
||||
</div>
|
||||
|
||||
{totalPages <= 7 && (
|
||||
<div className='join-item join gap-0.5'>
|
||||
{range(1, totalPages).map((pageNumber) => (
|
||||
<div className='join w-full justify-end @lg:justify-center items-center gap-0.5'>
|
||||
<div className='hidden @lg:block'>
|
||||
<GoToFirstPageButton />
|
||||
</div>
|
||||
|
||||
<div className='hidden @lg:block'>
|
||||
<PrevPageButton />
|
||||
</div>
|
||||
|
||||
{totalPages <= 7 &&
|
||||
range(1, totalPages).map((pageNumber) => (
|
||||
<PaginationButton
|
||||
key={pageNumber}
|
||||
content={pageNumber}
|
||||
@@ -138,195 +255,168 @@ const Pagination = ({
|
||||
onClick={() => pageChangeHandler(pageNumber)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{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 && (
|
||||
{totalPages > 7 && (
|
||||
<>
|
||||
<PaginationButton
|
||||
content={totalPages}
|
||||
disabled={currentPage === totalPages}
|
||||
onClick={() => pageChangeHandler(totalPages)}
|
||||
content={1}
|
||||
disabled={currentPage === 1}
|
||||
onClick={() => pageChangeHandler(1)}
|
||||
/>
|
||||
)}
|
||||
</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'
|
||||
{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
|
||||
content={totalPages}
|
||||
disabled={currentPage === totalPages}
|
||||
onClick={() => pageChangeHandler(totalPages)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
>
|
||||
Next{' '}
|
||||
<Icon
|
||||
icon='uil:arrow-right'
|
||||
width={20}
|
||||
height={20}
|
||||
className='text-gray-400 group-disabled:text-gray-300'
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div className='hidden @lg:block'>
|
||||
<NextPageButton />
|
||||
</div>
|
||||
|
||||
<div className='hidden @lg:block'>
|
||||
<GoToLastPageButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='hidden @lg:block'>
|
||||
<PageInfo />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 mt-2 sm:hidden'>
|
||||
<button
|
||||
disabled={currentPage === 1}
|
||||
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 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='flex @lg:hidden flex-col justify-center items-end gap-2'>
|
||||
<div className='flex flex-row items-center gap-0.5'>
|
||||
<GoToFirstPageButton />
|
||||
<PrevPageButton />
|
||||
<NextPageButton />
|
||||
<GoToLastPageButton />
|
||||
</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 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 className='flex flex-row items-center gap-4'>
|
||||
<DisplayedRowCountSelect />
|
||||
|
||||
<PageInfo />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+59
-29
@@ -38,6 +38,7 @@ export interface TableProps<TData extends object> {
|
||||
data: TData[];
|
||||
columns: ColumnDef<TData, unknown>[];
|
||||
pageSize?: number;
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
totalItems?: number;
|
||||
page?: number;
|
||||
onPageChange?: (page: number) => void;
|
||||
@@ -52,6 +53,8 @@ export interface TableProps<TData extends object> {
|
||||
rowSelection?: Record<string, boolean>;
|
||||
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
||||
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
||||
withCheckbox?: boolean;
|
||||
rowOptions?: number[];
|
||||
}
|
||||
|
||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||
@@ -64,28 +67,32 @@ const emptyContentDefaultValue = (
|
||||
</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>({
|
||||
data = [],
|
||||
columns = [],
|
||||
pageSize = 10,
|
||||
onPageSizeChange,
|
||||
totalItems,
|
||||
page,
|
||||
onPageChange,
|
||||
isLoading = false,
|
||||
fuzzySearchValue,
|
||||
onFuzzySearchValueChange,
|
||||
className = {
|
||||
containerClassName: '',
|
||||
tableWrapperClassName: '',
|
||||
tableClassName: '',
|
||||
tableHeaderClassName: '',
|
||||
headerRowClassName: '',
|
||||
headerColumnClassName: '',
|
||||
tableBodyClassName: '',
|
||||
bodyRowClassName: '',
|
||||
bodyColumnClassName: '',
|
||||
paginationClassName: '',
|
||||
},
|
||||
className = TABLE_DEFAULT_STYLING,
|
||||
emptyContent = emptyContentDefaultValue,
|
||||
sorting,
|
||||
setSorting,
|
||||
@@ -93,12 +100,19 @@ const Table = <TData extends object>({
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
enableRowSelection,
|
||||
withCheckbox = false,
|
||||
rowOptions = [10, 20, 50, 100],
|
||||
}: TableProps<TData>) => {
|
||||
const isServerSideTable =
|
||||
totalItems !== undefined &&
|
||||
page !== undefined &&
|
||||
onPageChange !== undefined;
|
||||
|
||||
const tableClassNames = {
|
||||
...TABLE_DEFAULT_STYLING,
|
||||
...className,
|
||||
};
|
||||
|
||||
const [pagination, setPagination] = useState({
|
||||
pageIndex: 0,
|
||||
pageSize: pageSize,
|
||||
@@ -191,12 +205,15 @@ const Table = <TData extends object>({
|
||||
}, [pageSize, setPageSize]);
|
||||
|
||||
return (
|
||||
<div className={className.containerClassName}>
|
||||
<div className={className.tableWrapperClassName}>
|
||||
<table className={className.tableClassName}>
|
||||
<thead className={className.tableHeaderClassName}>
|
||||
<div className={tableClassNames.containerClassName}>
|
||||
<div className={tableClassNames.tableWrapperClassName}>
|
||||
<table className={tableClassNames.tableClassName}>
|
||||
<thead className={tableClassNames.tableHeaderClassName}>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id} className={className.headerRowClassName}>
|
||||
<tr
|
||||
key={headerGroup.id}
|
||||
className={tableClassNames.headerRowClassName}
|
||||
>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
@@ -206,7 +223,10 @@ const Table = <TData extends object>({
|
||||
header.column.getCanSort()
|
||||
? 'cursor-pointer select-none'
|
||||
: '',
|
||||
className.headerColumnClassName
|
||||
{
|
||||
'first:w-9 first:pr-0': withCheckbox,
|
||||
},
|
||||
tableClassNames.headerColumnClassName
|
||||
)}
|
||||
>
|
||||
<div className='flex items-center gap-1'>
|
||||
@@ -216,12 +236,13 @@ const Table = <TData extends object>({
|
||||
)}
|
||||
|
||||
{header.column.getCanSort() && (
|
||||
<div className='flex items-center'>
|
||||
<div className='w-4 h-4 relative flex flex-col items-center'>
|
||||
<Icon
|
||||
icon='lucide:arrow-up'
|
||||
width={12}
|
||||
height={12}
|
||||
icon='heroicons:chevron-up-16-solid'
|
||||
width={18}
|
||||
height={18}
|
||||
className={cn(
|
||||
'absolute -top-1',
|
||||
'transition-all ease-in-out duration-200',
|
||||
header.column.getIsSorted() === 'asc'
|
||||
? 'text-black'
|
||||
@@ -229,10 +250,11 @@ const Table = <TData extends object>({
|
||||
)}
|
||||
/>
|
||||
<Icon
|
||||
icon='lucide:arrow-down'
|
||||
width={12}
|
||||
height={12}
|
||||
icon='heroicons:chevron-down-16-solid'
|
||||
width={18}
|
||||
height={18}
|
||||
className={cn(
|
||||
'absolute -bottom-1.5',
|
||||
'transition-all ease-in-out duration-200',
|
||||
header.column.getIsSorted() === 'desc'
|
||||
? 'text-black'
|
||||
@@ -248,11 +270,17 @@ const Table = <TData extends object>({
|
||||
))}
|
||||
</thead>
|
||||
|
||||
<tbody className={className.tableBodyClassName}>
|
||||
<tbody className={tableClassNames.tableBodyClassName}>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id} className={className.bodyRowClassName}>
|
||||
<tr key={row.id} className={tableClassNames.bodyRowClassName}>
|
||||
{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 &&
|
||||
flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
|
||||
@@ -270,7 +298,7 @@ const Table = <TData extends object>({
|
||||
emptyContent}
|
||||
|
||||
{data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && (
|
||||
<div className={cn('mt-5', className.paginationClassName)}>
|
||||
<div className={cn('mt-5', tableClassNames.paginationClassName)}>
|
||||
<Pagination
|
||||
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
||||
itemsPerPage={table.getState().pagination.pageSize}
|
||||
@@ -282,6 +310,8 @@ const Table = <TData extends object>({
|
||||
onPrevPage={prevPageClickHandler}
|
||||
onNextPage={nextPageClickHandler}
|
||||
onPageChange={pageChangeHandler}
|
||||
rowOptions={rowOptions}
|
||||
onRowChange={onPageSizeChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import { HTMLProps, useEffect, useRef } from 'react';
|
||||
import { cn } from '@/lib/helper';
|
||||
import { Size } from '@/types/theme';
|
||||
|
||||
interface CheckboxInputProps extends HTMLProps<HTMLInputElement> {
|
||||
interface CheckboxInputProps extends Omit<HTMLProps<HTMLInputElement>, 'size'> {
|
||||
name: string;
|
||||
label?: string;
|
||||
indeterminate?: boolean;
|
||||
@@ -16,6 +17,7 @@ interface CheckboxInputProps extends HTMLProps<HTMLInputElement> {
|
||||
isError?: boolean;
|
||||
isValid?: boolean;
|
||||
errorMessage?: string;
|
||||
size?: Size;
|
||||
}
|
||||
|
||||
const CheckboxInput = ({
|
||||
@@ -27,10 +29,19 @@ const CheckboxInput = ({
|
||||
isValid,
|
||||
isError,
|
||||
errorMessage,
|
||||
size = 'sm',
|
||||
...rest
|
||||
}: CheckboxInputProps) => {
|
||||
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(() => {
|
||||
if (typeof indeterminate === 'boolean') {
|
||||
ref.current.indeterminate = !rest.checked && indeterminate;
|
||||
@@ -53,7 +64,7 @@ const CheckboxInput = ({
|
||||
id={name}
|
||||
name={name}
|
||||
className={cn(
|
||||
'checkbox cursor-pointer',
|
||||
checkboxBaseClassName,
|
||||
{
|
||||
'border-error': isError,
|
||||
'border-success': isValid,
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { cn } from '@/lib/helper';
|
||||
import { Size } from '@/types/theme';
|
||||
|
||||
interface MenuProps {
|
||||
children?: ReactNode;
|
||||
size?: Size;
|
||||
direction?: 'vertical' | 'horizontal';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Menu = ({ children, className }: MenuProps) => {
|
||||
return (
|
||||
<ul className={cn('menu w-full p-0 gap-0.5', className)}>{children}</ul>
|
||||
);
|
||||
const Menu = ({
|
||||
children,
|
||||
size = 'md',
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
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;
|
||||
+34
-73
@@ -1,155 +1,116 @@
|
||||
type MAIN_DRAWER_MENU = {
|
||||
title: string;
|
||||
link: string;
|
||||
icon: string;
|
||||
submenu?: MAIN_DRAWER_MENU[];
|
||||
};
|
||||
import { SidebarMenuItem } from '@/components/molecules/SidebarMenu';
|
||||
|
||||
export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [
|
||||
export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
text: 'Dashboard',
|
||||
link: '/dashboard',
|
||||
icon: 'gg:chart',
|
||||
icon: 'heroicons-outline:chart-bar-square',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Produksi',
|
||||
text: 'Produksi',
|
||||
link: '/production',
|
||||
icon: 'material-symbols:conveyor-belt-outline-rounded',
|
||||
icon: 'heroicons-outline:wrench-screwdriver',
|
||||
submenu: [
|
||||
{
|
||||
title: 'List Flock',
|
||||
text: 'Daftar 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',
|
||||
// },
|
||||
{
|
||||
title: 'Recording',
|
||||
text: 'Recording',
|
||||
link: '/production/recording',
|
||||
icon: 'mdi:clipboard-text',
|
||||
},
|
||||
{
|
||||
title: 'Transfer ke Laying',
|
||||
text: 'Transfer to Laying',
|
||||
link: '/production/transfer-to-laying',
|
||||
icon: 'streamline:transfer-van',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Pembelian',
|
||||
text: 'Pembelian',
|
||||
link: '/purchase',
|
||||
icon: 'gg:shopping-cart',
|
||||
icon: 'heroicons-outline:shopping-cart',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Penjualan',
|
||||
text: 'Penjualan',
|
||||
link: '/marketing',
|
||||
icon: 'mdi:attach-money',
|
||||
icon: 'heroicons-outline:currency-dollar',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Biaya Operasional',
|
||||
text: 'Biaya Operasional',
|
||||
link: '/expense',
|
||||
icon: 'uil:wallet',
|
||||
icon: 'heroicons:wallet',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Persediaan',
|
||||
text: 'Persediaan',
|
||||
link: '/inventory',
|
||||
icon: 'mdi:warehouse',
|
||||
icon: 'heroicons-outline:folder',
|
||||
submenu: [
|
||||
// {
|
||||
// title: 'Product',
|
||||
// link: '/inventory/product',
|
||||
// icon: 'mdi:package-variant-closed',
|
||||
// },
|
||||
{
|
||||
title: 'Penyesuaian Stok',
|
||||
text: 'Penyesuaian Stok',
|
||||
link: '/inventory/adjustment',
|
||||
icon: 'mdi:database-edit',
|
||||
},
|
||||
{
|
||||
title: 'Transfer Stok',
|
||||
text: 'Transfer Stok',
|
||||
link: '/inventory/movement',
|
||||
icon: 'mdi:swap-horizontal',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Master Data',
|
||||
text: 'Master Data',
|
||||
link: '/master-data',
|
||||
icon: 'majesticons:data-line',
|
||||
icon: 'heroicons-outline:circle-stack',
|
||||
submenu: [
|
||||
{
|
||||
title: 'Product',
|
||||
text: 'Produk',
|
||||
link: '/master-data/product',
|
||||
icon: 'fluent-mdl2:product-variant',
|
||||
},
|
||||
{
|
||||
title: 'Product Category',
|
||||
text: 'Kategori Produk',
|
||||
link: '/master-data/product-category',
|
||||
icon: 'carbon:categories',
|
||||
},
|
||||
{
|
||||
title: 'Bank',
|
||||
text: 'Bank',
|
||||
link: '/master-data/bank',
|
||||
icon: 'mdi:bank-outline',
|
||||
},
|
||||
{
|
||||
title: 'Area',
|
||||
text: 'Area',
|
||||
link: '/master-data/area',
|
||||
icon: 'majesticons:map-marker-area-line',
|
||||
},
|
||||
{
|
||||
title: 'Location',
|
||||
text: 'Lokasi',
|
||||
link: '/master-data/location',
|
||||
icon: 'mingcute:location-line',
|
||||
},
|
||||
{
|
||||
title: 'Kandang',
|
||||
text: 'Kandang',
|
||||
link: '/master-data/kandang',
|
||||
icon: 'mdi:farm-home-outline',
|
||||
},
|
||||
{
|
||||
title: 'Warehouse',
|
||||
text: 'Warehouse',
|
||||
link: '/master-data/warehouse',
|
||||
icon: 'hugeicons:warehouse',
|
||||
},
|
||||
{
|
||||
title: 'Customer',
|
||||
text: 'Customer',
|
||||
link: '/master-data/customer',
|
||||
icon: 'ix:customer',
|
||||
},
|
||||
{
|
||||
title: 'UOM',
|
||||
text: 'UOM',
|
||||
link: '/master-data/uom',
|
||||
icon: 'lsicon:measure-outline',
|
||||
},
|
||||
{
|
||||
title: 'Non-Stock',
|
||||
text: 'Non-Stock',
|
||||
link: '/master-data/nonstock',
|
||||
icon: 'fluent:box-32-regular',
|
||||
},
|
||||
{
|
||||
title: 'FCR',
|
||||
text: 'FCR',
|
||||
link: '/master-data/fcr',
|
||||
icon: 'fluent:food-chicken-leg-16-regular',
|
||||
},
|
||||
{
|
||||
title: 'Supplier',
|
||||
text: 'Supplier',
|
||||
link: '/master-data/supplier',
|
||||
icon: 'material-symbols:add-business-outline-rounded',
|
||||
},
|
||||
{
|
||||
title: 'Flock',
|
||||
text: 'Flock',
|
||||
link: '/master-data/flock',
|
||||
icon: 'material-symbols:raven-outline-rounded',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -119,3 +119,16 @@ export const convertRowSelectionObjToArr = (
|
||||
|
||||
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,4 +1,9 @@
|
||||
@layer utilities {
|
||||
.menu {
|
||||
--menu-active-fg: var(--color-primary);
|
||||
--menu-active-bg: transparent;
|
||||
}
|
||||
|
||||
.step.step-success::before {
|
||||
--step-bg: var(--color-success);
|
||||
--step-fg: var(--color-success-content);
|
||||
|
||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
||||
type Color =
|
||||
export type Color =
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'accent'
|
||||
@@ -9,4 +9,4 @@ type Color =
|
||||
| 'error'
|
||||
| 'none';
|
||||
|
||||
export { Color };
|
||||
export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
Reference in New Issue
Block a user