diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 951e5472..c37bfd35 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 < 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 diff --git a/src/app/globals.css b/src/app/globals.css index e50e020d..8f7ca2f4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -7,26 +7,39 @@ default: false; prefersdark: false; color-scheme: 'light'; - --color-base-100: oklch(98% 0.001 106.423); - --color-base-200: oklch(97% 0.001 106.424); - --color-base-300: oklch(92% 0.003 48.717); - --color-base-content: oklch(22.389% 0.031 278.072); - --color-primary: oklch(60% 0.126 221.723); - --color-primary-content: oklch(100% 0 0); - --color-secondary: oklch(52% 0.105 223.128); - --color-secondary-content: oklch(100% 0 0); - --color-accent: oklch(45% 0.085 224.283); - --color-accent-content: oklch(100% 0 0); - --color-neutral: oklch(39% 0.07 227.392); - --color-neutral-content: oklch(100% 0 0); - --color-info: oklch(58% 0.158 241.966); - --color-info-content: oklch(100% 0 0); - --color-success: oklch(62% 0.194 149.214); - --color-success-content: oklch(100% 0 0); - --color-warning: oklch(85% 0.199 91.936); - --color-warning-content: oklch(0% 0 0); - --color-error: oklch(57% 0.245 27.325); - --color-error-content: oklch(100% 0 0); + + /* Primary Colors */ + --color-primary: oklch(39.4% 0.177 301.9); + --color-primary-content: oklch(87.5% 0.038 274.5); + + /* Secondary Colors */ + --color-secondary: oklch(60.1% 0.258 335.7); + --color-secondary-content: oklch(99.4% 0.007 337.8); + + /* Accent Colors */ + --color-accent: oklch(76.2% 0.155 170.8); + --color-accent-content: oklch(7.2% 0.007 167.6); + + /* Neutral Colors */ + --color-neutral: oklch(22.4% 0.032 258.8); + --color-neutral-content: oklch(87.7% 0.016 257); + + /* Base Colors */ + --color-base-100: oklch(100% 0 0); /* #ffffff */ + --color-base-200: oklch(97.2% 0 0); /* #f2f2f2 */ + --color-base-300: oklch(93.1% 0.002 249.7); /* #e5e6e6 */ + --color-base-content: oklch(18.6% 0.024 257.7); /* #1f2937 */ + + /* Status/Utility Colors */ + --color-info: oklch(67.4% 0.176 238.9); + --color-info-content: oklch(0% 0 0); /* #000000 */ + --color-success: oklch(62.3% 0.147 149); + --color-success-content: oklch(100% 0 0); /* #ffffff */ + --color-warning: oklch(82.2% 0.165 91.9); + --color-warning-content: oklch(0% 0 0); /* #000000 */ + --color-error: oklch(61.8% 0.203 27.8); + --color-error-content: oklch(100% 0 0); /* #fffffff */ + --radius-selector: 0rem; --radius-field: 0.25rem; --radius-box: 0.25rem; diff --git a/src/app/inventory/adjustment/detail/page.tsx b/src/app/inventory/adjustment/detail/page.tsx index acb9f8db..eb13647d 100644 --- a/src/app/inventory/adjustment/detail/page.tsx +++ b/src/app/inventory/adjustment/detail/page.tsx @@ -12,8 +12,6 @@ const DetailInventoryAdjustment = () => { // Ambil data dari router state useEffect(() => { - console.log('Router State'); - console.log(window.history.state); const state = window.history.state?.usr as | { inventoryAdjustment?: InventoryAdjustment } | undefined; @@ -26,9 +24,6 @@ const DetailInventoryAdjustment = () => { const finalData = inventoryAdjustment; - console.log('Final Data'); - console.log(finalData); - if (!finalData) { return (
diff --git a/src/app/inventory/product/detail/layout.tsx b/src/app/inventory/product/detail/layout.tsx new file mode 100644 index 00000000..7220dfa1 --- /dev/null +++ b/src/app/inventory/product/detail/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from '@/components/helper/SuspenseHelper'; + +const Layout = ({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) => { + return {children}; +}; + +export default Layout; diff --git a/src/app/inventory/product/detail/page.tsx b/src/app/inventory/product/detail/page.tsx new file mode 100644 index 00000000..6daa7a86 --- /dev/null +++ b/src/app/inventory/product/detail/page.tsx @@ -0,0 +1,50 @@ +'use client'; + +import InventoryProductDetail from '@/components/pages/inventory/product/detail/InventoryProductDetail'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { InventoryProductApi } from '@/services/api/inventory'; +import { useRouter, useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; + +const InventoryProductDetailPage = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + const inventoryProductId = searchParams.get('inventoryProductId'); + + const { data: inventoryProduct, isLoading: isLoadingInventoryProduct } = + useSWR(inventoryProductId, (id: number) => + InventoryProductApi.getSingle(id) + ); + + if (!inventoryProductId) { + router.back(); + + return ( +
+ +
+ ); + } + + if ( + !isLoadingInventoryProduct && + (!inventoryProduct || isResponseError(inventoryProduct)) + ) { + router.replace('/404'); + return; + } + + return ( +
+ {isLoadingInventoryProduct && ( + + )} + {!isLoadingInventoryProduct && isResponseSuccess(inventoryProduct) && ( + + )} +
+ ); +}; + +export default InventoryProductDetailPage; diff --git a/src/app/inventory/product/page.tsx b/src/app/inventory/product/page.tsx new file mode 100644 index 00000000..4815b8a1 --- /dev/null +++ b/src/app/inventory/product/page.tsx @@ -0,0 +1,11 @@ +import InventoryProductTable from '@/components/pages/inventory/product/InventoryProductTable'; + +const InventoryProductPage = () => { + return ( +
+ +
+ ); +}; + +export default InventoryProductPage; diff --git a/src/app/production/project-flock/add/page.tsx b/src/app/production/project-flock/add/page.tsx index b323b5f3..2eb2c090 100644 --- a/src/app/production/project-flock/add/page.tsx +++ b/src/app/production/project-flock/add/page.tsx @@ -1,10 +1,18 @@ 'use client'; import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm'; +import React, { useImperativeHandle } from 'react'; +import toast from 'react-hot-toast'; const AddProjectFlock = () => { + // useImperativeHandle(ref, () => ({ + // validate() { + // toast.success('Validating'); + // return false; + // }, + // })); return ( -
+
); diff --git a/src/app/production/project-flock/chickin/add/kandang/page.tsx b/src/app/production/project-flock/chickin/add/kandang/page.tsx index a22039d1..c3a93a80 100644 --- a/src/app/production/project-flock/chickin/add/kandang/page.tsx +++ b/src/app/production/project-flock/chickin/add/kandang/page.tsx @@ -44,7 +44,7 @@ export default function AddChickinKandang() { return ( <> -
+
{isLoading && } {!isLoading && isResponseSuccess(projectFlockKandang) && diff --git a/src/app/production/project-flock/chickin/add/page.tsx b/src/app/production/project-flock/chickin/add/page.tsx index bcb4d612..831979cb 100644 --- a/src/app/production/project-flock/chickin/add/page.tsx +++ b/src/app/production/project-flock/chickin/add/page.tsx @@ -10,7 +10,7 @@ const AddChickin = () => { return ( <> -
+
diff --git a/src/app/production/project-flock/chickin/page.tsx b/src/app/production/project-flock/chickin/page.tsx index 5d105aab..d40c39a3 100644 --- a/src/app/production/project-flock/chickin/page.tsx +++ b/src/app/production/project-flock/chickin/page.tsx @@ -2,7 +2,7 @@ import ChickinTable from '@/components/pages/production/chickin/ChickinTable'; const Chickin = () => { return ( -
+
); diff --git a/src/app/production/project-flock/closing/layout.tsx b/src/app/production/project-flock/closing/layout.tsx new file mode 100644 index 00000000..7220dfa1 --- /dev/null +++ b/src/app/production/project-flock/closing/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from '@/components/helper/SuspenseHelper'; + +const Layout = ({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) => { + return {children}; +}; + +export default Layout; diff --git a/src/app/production/project-flock/closing/page.tsx b/src/app/production/project-flock/closing/page.tsx new file mode 100644 index 00000000..d734f669 --- /dev/null +++ b/src/app/production/project-flock/closing/page.tsx @@ -0,0 +1,63 @@ +'use client'; +import ProjectFlockClosingForm from '@/components/pages/production/project-flock/closing/ProjectFlockClosingForm'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { ProjectFlockKandangApi } from '@/services/api/production'; +import { ProjectFlockApi } from '@/services/api/production/project-flock'; +import { useRouter, useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; + +const ProjectFlockClosingPage = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + const projectFlockId = searchParams.get('projectFlockId'); + const projectFlockKandangId = searchParams.get('projectFlockKandangId'); + + const { data: projectFlockKandang, isLoading: isLoadingProjectFlockKandang } = + useSWR(projectFlockKandangId, (id: number) => + ProjectFlockKandangApi.getSingle(id) + ); + + const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR( + projectFlockId, + (id: number) => ProjectFlockApi.getSingle(id) + ); + + if (!projectFlockId || !projectFlockKandangId) { + router.back(); + + return ( +
+ +
+ ); + } + + if ( + !isLoadingProjectFlock && + (!projectFlock || isResponseError(projectFlock)) && + !isLoadingProjectFlockKandang && + (!projectFlockKandang || isResponseError(projectFlockKandang)) + ) { + router.replace('/404'); + return; + } + + return ( +
+ {isLoadingProjectFlock || + (isLoadingProjectFlockKandang && ( + + ))} + {isResponseSuccess(projectFlock) && + isResponseSuccess(projectFlockKandang) && ( + + )} +
+ ); +}; + +export default ProjectFlockClosingPage; diff --git a/src/app/production/project-flock/detail/edit/page.tsx b/src/app/production/project-flock/detail/edit/page.tsx index f55ce601..e5f88f19 100644 --- a/src/app/production/project-flock/detail/edit/page.tsx +++ b/src/app/production/project-flock/detail/edit/page.tsx @@ -37,7 +37,7 @@ const ProjectFlockEdit = () => { } return ( -
+
{isLoadingProjectFlock && ( )} diff --git a/src/app/production/project-flock/detail/page.tsx b/src/app/production/project-flock/detail/page.tsx index 91d4dfd5..29a078dd 100644 --- a/src/app/production/project-flock/detail/page.tsx +++ b/src/app/production/project-flock/detail/page.tsx @@ -1,12 +1,13 @@ 'use client'; +import ProjectFlockDetail from '@/components/pages/production/project-flock/detail/ProjectFlockDetail'; import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { ProjectFlockApi } from '@/services/api/production/project-flock'; import { useRouter, useSearchParams } from 'next/navigation'; import useSWR from 'swr'; -const ProjectFlockDetail = () => { +const ProjectFlockDetailPage = () => { const router = useRouter(); const searchParams = useSearchParams(); @@ -37,19 +38,17 @@ const ProjectFlockDetail = () => { } return ( -
+
{isLoadingProjectFlock && ( )} {isResponseSuccess(projectFlock) && ( - + )}
); }; -export default ProjectFlockDetail; +export default ProjectFlockDetailPage; +ProjectFlockDetail; +ProjectFlockDetail; diff --git a/src/app/production/project-flock/layout.tsx b/src/app/production/project-flock/layout.tsx new file mode 100644 index 00000000..698064cf --- /dev/null +++ b/src/app/production/project-flock/layout.tsx @@ -0,0 +1,59 @@ +'use client'; + +import { usePathname, useRouter } from 'next/navigation'; +import Drawer from '@/components/Drawer'; +import React, { ReactNode } from 'react'; +import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable'; +import { useUiStore } from '@/stores/ui/ui.store'; + +export default function ProjectFlockLayout({ + children, +}: { + children: ReactNode; +}) { + const pathname = usePathname(); + const router = useRouter(); + const toggleValidate = useUiStore((s) => s.toggleValidate); + + const isAdd = pathname.endsWith('/add'); + const isEdit = pathname.includes('/detail/edit'); + const isDetail = pathname.includes('/detail'); + const isChickin = pathname.includes('/chickin/add/kandang'); + const isClosing = pathname.includes('/closing'); + + const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing; + + const handleBackdropClick = () => { + const unsub = useUiStore.getState().subscribeIsValid((isValid) => { + if (isValid) { + unsub(); // berhenti listen + router.push('/production/project-flock'); + } + }); + + toggleValidate(); + }; + + return ( + <> + {/* List page always rendered */} +
+ !isOpen && router.push('/production/project-flock')} + /> +
+ + {/* Render Drawer only on /add */} + { + if (!v) router.push('/production/project-flock'); + }} + closeOnBackdropClick={isDetail ? true : false} + onBackdropClick={handleBackdropClick} + variant='right' + sidebarContent={isOpen &&
{children}
} + /> + + ); +} diff --git a/src/app/production/project-flock/page.tsx b/src/app/production/project-flock/page.tsx index 79feb41f..e93c6bc4 100644 --- a/src/app/production/project-flock/page.tsx +++ b/src/app/production/project-flock/page.tsx @@ -2,7 +2,7 @@ import ProjectFlockTable from '@/components/pages/production/project-flock/Proje const ProjectFlock = () => { return ( -
+
); diff --git a/src/components/Card.tsx b/src/components/Card.tsx index d3ff80b1..ff4c35f2 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -4,7 +4,7 @@ import { HTMLAttributes, ReactNode, useState } from 'react'; import { cn } from '@/lib/helper'; import Image from 'next/image'; -import Collapse from './Collapse'; +import Collapse from '@/components/Collapse'; import { Icon } from '@iconify/react'; export interface CardProps diff --git a/src/components/Drawer.tsx b/src/components/Drawer.tsx index f0efb417..17b8a56f 100644 --- a/src/components/Drawer.tsx +++ b/src/components/Drawer.tsx @@ -10,28 +10,102 @@ interface DrawerProps { open: boolean; setOpen: (newOpenState: boolean) => void; openOnLarge?: boolean; + variant?: 'sidebar' | 'left' | 'right'; + zIndex?: string; + className?: DrawerClassName; + onBackdropClick?: () => void; + closeOnBackdropClick?: boolean; } +type DrawerClassName = { + drawer?: string; + drawerContent?: string; + drawerSide?: string; + drawerOverlay?: string; + drawerSidebarContent?: string; +}; + const Drawer = ({ children, sidebarContent, open, setOpen, openOnLarge, + variant = 'sidebar', + zIndex = '20', + className, + onBackdropClick, + closeOnBackdropClick = true, }: DrawerProps) => { + const getDrawerClassNames = (): DrawerClassName => { + const baseClassNames = { + drawer: 'drawer', + drawerContent: 'drawer-content', + drawerSide: 'drawer-side', + drawerOverlay: 'drawer-overlay', + drawerSidebarContent: 'min-h-full bg-base-100', + }; + + if (variant === 'sidebar') { + return { + ...baseClassNames, + drawerSidebarContent: cn( + baseClassNames.drawerSidebarContent, + 'w-full max-w-[300px] lg:w-[300px]' + ), + }; + } else if (variant === 'right') { + return { + ...baseClassNames, + drawer: cn(baseClassNames.drawer, 'drawer-end'), + drawerSide: cn( + baseClassNames.drawerSide, + 'border-l border-solid border-gray-200 drawer-side w-screen top-0 right-0 fixed z-21' + ), + drawerSidebarContent: cn( + baseClassNames.drawerSidebarContent, + 'w-full min-w-120 sm:w-fit' + ), + }; + } else if (variant === 'left') { + return { + ...baseClassNames, + drawerSide: cn( + baseClassNames.drawerSide, + 'border-l border-solid border-gray-200 drawer-side w-screen top-0 right-0 fixed z-21' + ), + drawerSidebarContent: cn( + baseClassNames.drawerSidebarContent, + 'w-full min-w-120 sm:w-fit' + ), + }; + } + return baseClassNames; // Fallback for default or unknown variant + }; + + const varianClassName = getDrawerClassNames(); + const toggleDrawer = () => { setOpen(!open); }; const closeDrawer = () => { - setOpen(false); + if (closeOnBackdropClick) { + setOpen(false); + } + onBackdropClick && onBackdropClick(); }; return (
-
{children}
+ {/* Drawer Content */} +
+ {children} +
-
+ {/* Drawer Side */} +