diff --git a/src/app/globals.css b/src/app/globals.css index 96339bf2..fde8d2b4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -68,6 +68,8 @@ --shadow-button-soft: 0 3px 2px -2px var(--color-base-200), 0 4px 3px -2px var(--color-base-200); + + --shadow-bg: 0px -2px 4px 0px #00000014; } html { diff --git a/src/app/marketing/add/delivery-orders/layout.tsx b/src/app/marketing/add/delivery-orders/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/add/delivery-orders/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/add/delivery-orders/page.tsx b/src/app/marketing/add/delivery-orders/page.tsx deleted file mode 100644 index 4d92acda..00000000 --- a/src/app/marketing/add/delivery-orders/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; - -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import toast from 'react-hot-toast'; -import useSWR from 'swr'; - -const EditMarketingDelivery = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(`get-so-${soId}`, () => - MarketingApi.getSingle(soId ? parseInt(soId) : 0) - ); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - { - refreshMarketing(); - }} - /> - )} -
- ); -}; -export default EditMarketingDelivery; diff --git a/src/app/marketing/add/sales-orders/page.tsx b/src/app/marketing/add/sales-orders/page.tsx deleted file mode 100644 index 9e33d304..00000000 --- a/src/app/marketing/add/sales-orders/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; - -const AddSalesOrder = () => { - return ( -
- -
- ); -}; - -export default AddSalesOrder; diff --git a/src/app/marketing/detail/delivery-orders/edit/layout.tsx b/src/app/marketing/detail/delivery-orders/edit/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/detail/delivery-orders/edit/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/detail/delivery-orders/edit/page.tsx b/src/app/marketing/detail/delivery-orders/edit/page.tsx deleted file mode 100644 index 32625026..00000000 --- a/src/app/marketing/detail/delivery-orders/edit/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client'; - -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import toast from 'react-hot-toast'; -import useSWR from 'swr'; - -const EditMarketingDelivery = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(`get-so-${soId}`, () => - MarketingApi.getSingle(soId ? parseInt(soId) : 0) - ); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - - if ( - isResponseSuccess(marketing) && - marketing.data.latest_approval.step_number != 3 - ) { - toast.error('Data Marketing perlu dilakukan approval terlebih dahulu!'); - router.back(); - } - - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - { - refreshMarketing(); - }} - /> - )} -
- ); -}; -export default EditMarketingDelivery; diff --git a/src/app/marketing/detail/layout.tsx b/src/app/marketing/detail/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/detail/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/detail/page.tsx b/src/app/marketing/detail/page.tsx deleted file mode 100644 index 902251e8..00000000 --- a/src/app/marketing/detail/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import MarketingDetail from '@/components/pages/marketing/detail/MarketingDetail'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; - -const DetailMarketing = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(soId, (id: number) => MarketingApi.getSingle(id)); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - - )} -
- ); -}; - -export default DetailMarketing; diff --git a/src/app/marketing/detail/sales-orders/edit/layout.tsx b/src/app/marketing/detail/sales-orders/edit/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/marketing/detail/sales-orders/edit/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/marketing/detail/sales-orders/edit/page.tsx b/src/app/marketing/detail/sales-orders/edit/page.tsx deleted file mode 100644 index 19a098c5..00000000 --- a/src/app/marketing/detail/sales-orders/edit/page.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import MarketingForm from '@/components/pages/marketing/form/MarketingForm'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { MarketingApi } from '@/services/api/marketing/marketing'; -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; - -const EditSalesOrder = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const soId = searchParams.get('marketingId'); - - const { - data: marketing, - isLoading: isLoading, - mutate: refreshMarketing, - } = useSWR(`get-so-${soId}`, () => - MarketingApi.getSingle(soId ? parseInt(soId) : 0) - ); - - if (!soId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoading && (!marketing || isResponseError(marketing))) { - router.replace('/404'); - return; - } - return ( -
- {isLoading && } - {!isLoading && isResponseSuccess(marketing) && ( - { - refreshMarketing(); - }} - /> - )} -
- ); -}; -export default EditSalesOrder; diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index c30ee501..d8b4bdcf 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -1,9 +1,14 @@ +import DeliveryOrderFormModal from '@/components/pages/marketing/DeliveryOrderFormModal'; import MarketingTable from '@/components/pages/marketing/MarketingTable'; +import SalesOrderFormModal from '@/components/pages/marketing/SalesOrderFormModal'; const Marketing = () => { return ( -
+
+ + +
); }; diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 00000000..b2b82a77 --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,5 @@ +import PageNotFound from '@/components/helper/NotFoundPage'; + +export default function NotFound() { + return ; +} diff --git a/src/app/production/project-flock/detail/page.tsx b/src/app/production/project-flock/detail/page.tsx index 29a078dd..6187898e 100644 --- a/src/app/production/project-flock/detail/page.tsx +++ b/src/app/production/project-flock/detail/page.tsx @@ -50,5 +50,3 @@ const ProjectFlockDetailPage = () => { }; export default ProjectFlockDetailPage; -ProjectFlockDetail; -ProjectFlockDetail; diff --git a/src/app/production/project-flock/layout.tsx b/src/app/production/project-flock/layout.tsx index 3e9a65b7..1b1b4edf 100644 --- a/src/app/production/project-flock/layout.tsx +++ b/src/app/production/project-flock/layout.tsx @@ -1,10 +1,10 @@ 'use client'; import { usePathname, useRouter } from 'next/navigation'; -import Drawer from '@/components/Drawer'; -import React, { ReactNode } from 'react'; +import React, { ReactNode, useEffect } from 'react'; import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable'; import { useUiStore } from '@/stores/ui/ui.store'; +import Modal, { useModal } from '@/components/Modal'; export default function ProjectFlockLayout({ children, @@ -23,9 +23,12 @@ export default function ProjectFlockLayout({ const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing; + const formModal = useModal(); + const handleBackdropClick = () => { const unsub = useUiStore.getState().subscribeIsValid((isValid) => { if (isValid) { + formModal.closeModal(); unsub(); // berhenti listen router.push('/production/project-flock'); } @@ -34,6 +37,14 @@ export default function ProjectFlockLayout({ toggleValidate(); }; + useEffect(() => { + if (isOpen && !formModal.open) { + formModal.openModal(); + } else { + formModal.closeModal(); + } + }, [isOpen]); + return ( <> {/* List page always rendered */} @@ -43,18 +54,19 @@ export default function ProjectFlockLayout({ />
- {/* Render Drawer only on /add */} - { - if (!v) router.push('/production/project-flock'); - }} - closeOnBackdropClick={isDetail ? true : false} + {/* Render Modal only on /add */} + {children}} - /> + className={{ + modalBox: 'w-full sm:w-fit p-3 rounded-xl bg-transparent shadow-none', + }} + > +
+ {isOpen && children} +
+
); } diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx index 61792d0c..6b243cf9 100644 --- a/src/components/Alert.tsx +++ b/src/components/Alert.tsx @@ -1,15 +1,16 @@ -import { ReactNode } from 'react'; +import { ReactNode, Ref } from 'react'; import { cn } from '@/lib/helper'; interface AlertProps { + ref?: Ref | undefined; variant?: 'outline' | 'dash' | 'soft'; color?: 'info' | 'success' | 'warning' | 'error'; children?: ReactNode; className?: string; } -const Alert = ({ children, variant, color, className }: AlertProps) => { +const Alert = ({ children, ref, variant, color, className }: AlertProps) => { const alertBaseClassName = cn('alert', { 'alert-soft': variant === 'soft', 'alert-outline': variant === 'outline', @@ -21,7 +22,11 @@ const Alert = ({ children, variant, color, className }: AlertProps) => { 'alert-error': color === 'error', }); - return
{children}
; + return ( +
+ {children} +
+ ); }; export default Alert; diff --git a/src/components/MainDrawer.tsx b/src/components/MainDrawer.tsx index fdb65c38..71da0789 100644 --- a/src/components/MainDrawer.tsx +++ b/src/components/MainDrawer.tsx @@ -74,6 +74,8 @@ const MainDrawer = ({ const formattedPathname = pathname.endsWith('/') ? pathname : `${pathname}/`; + const isPathnameNotFoundPage = formattedPathname === '/404/'; + const isPermitted = ROUTE_PERMISSIONS[formattedPathname]?.some((permission) => permissionCheck(permission) ); @@ -82,10 +84,14 @@ const MainDrawer = ({ setMainDrawerOpen(!mainDrawerOpen); }; - if (!isPermitted) { + if (!isPermitted && !isPathnameNotFoundPage) { return ; } + if (isPathnameNotFoundPage) { + return children; + } + return (

- Progress Details + {title}

{ + return ( +
+

Halaman Tidak Ditemukan

+

+ Halaman atau data yang anda cari tidak ditemukan. +

+ +
+ ); +}; + +export default PageNotFound; diff --git a/src/components/helper/StatusBadge.tsx b/src/components/helper/StatusBadge.tsx index f9725fff..d1b32a24 100644 --- a/src/components/helper/StatusBadge.tsx +++ b/src/components/helper/StatusBadge.tsx @@ -1,24 +1,30 @@ +import { ReactNode } from 'react'; + import Badge from '@/components/Badge'; + import { cn } from '@/lib/helper'; import { Color } from '@/types/theme'; interface StatusBadgeProps { color: Color; - text: string; + text: ReactNode; className?: { badge?: string; status?: string; }; + onClick?: () => void; } const StatusBadge = ({ color = 'neutral', text, className, + onClick, }: StatusBadgeProps) => { return ( diff --git a/src/components/helper/drawer/DrawerHeader.tsx b/src/components/helper/drawer/DrawerHeader.tsx index 8bb635ae..b3fe24f1 100644 --- a/src/components/helper/drawer/DrawerHeader.tsx +++ b/src/components/helper/drawer/DrawerHeader.tsx @@ -27,7 +27,7 @@ export interface DrawerHeaderProps { const DrawerHeader = ({ leftIcon = 'mdi:close', - leftIconSize = 24, + leftIconSize = 20, leftIconHref, leftIconOnClick, leftIconClassName, @@ -43,7 +43,7 @@ const DrawerHeader = ({ icon={leftIcon} width={leftIconSize} height={leftIconSize} - className={cn('cursor-pointer', leftIconClassName)} + className={cn('cursor-pointer text-base-content ', leftIconClassName)} /> ); @@ -73,7 +73,7 @@ const DrawerHeader = ({ return (
@@ -82,7 +82,7 @@ const DrawerHeader = ({ {renderLeftIcon()} {showDivider && subtitle && ( -
+
)} {subtitle && ( diff --git a/src/components/helper/form/FormErrors.tsx b/src/components/helper/form/FormErrors.tsx index 1fd7b58f..e1de97ed 100644 --- a/src/components/helper/form/FormErrors.tsx +++ b/src/components/helper/form/FormErrors.tsx @@ -1,7 +1,10 @@ +'use client'; + import Alert from '@/components/Alert'; import Button from '@/components/Button'; +import { cn } from '@/lib/helper'; import { Icon } from '@iconify/react'; -import { useState } from 'react'; +import { useEffect, useRef } from 'react'; /** * Alert Unique Error List @@ -10,34 +13,81 @@ import { useState } from 'react'; */ const AlertErrorList = ({ formErrorList, + className, onClose, + title, }: { formErrorList: string[]; + className?: { + alert?: string; + button?: string; + headerWrapper?: string; + headerIcon?: string; + headerText?: string; + titleWrapper?: string; + ul?: string; + li?: string; + }; onClose: () => void; + title?: string; }) => { + const alertRef = useRef(null); + + useEffect(() => { + if (formErrorList.length > 0) { + alertRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + } + }, [formErrorList.length]); + if (formErrorList.length === 0) return null; return ( - -
-
- - - Terdapat {formErrorList.length} error pada form: + +
+
+ + + {title || `Terdapat ${formErrorList.length} error pada form:`}
-
    +
      {formErrorList.map((error, index) => ( -
    • +
    • {error}
    • ))} diff --git a/src/components/input/DebouncedTextArea.tsx b/src/components/input/DebouncedTextArea.tsx index 3df2c032..24d4b4e7 100644 --- a/src/components/input/DebouncedTextArea.tsx +++ b/src/components/input/DebouncedTextArea.tsx @@ -7,6 +7,7 @@ import TextArea, { TextAreaProps } from '@/components/input/TextArea'; interface DebouncedTextAreaProps extends TextAreaProps { delay?: number; + ref?: React.RefObject; } const DebouncedTextArea = (props: DebouncedTextAreaProps) => { @@ -19,6 +20,11 @@ const DebouncedTextArea = (props: DebouncedTextAreaProps) => { const [debouncedChangeEvent] = useDebounce(internalChangeEvent, delay ?? 300); const [debouncedValue] = useDebounce(internalValue, delay ?? 300); + // Sync internal value with external props.value when it changes (e.g., form reset) + useEffect(() => { + setInternalValue(props.value); + }, [props.value]); + const internalChangeHandler: ChangeEventHandler = ( e ) => { @@ -35,6 +41,7 @@ const DebouncedTextArea = (props: DebouncedTextAreaProps) => { return (