mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into dev/restu
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
npm run format
|
npm run format
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run build
|
npx tsc --noEmit
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const FinanceAdjust = () => {
|
||||||
|
return <div>Finance Adjust</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinanceAdjust;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import FormFinanceAddInitialBalance from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance';
|
||||||
|
|
||||||
|
const FinanceAddInitialBalancePage = () => {
|
||||||
|
return <FormFinanceAddInitialBalance type='add' />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinanceAddInitialBalancePage;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import FormFinanceInjection from '@/components/pages/finance/add/injection/FormFinanceInjection';
|
||||||
|
|
||||||
|
const FinanceAddInjectionPage = () => {
|
||||||
|
return <FormFinanceInjection type='add' />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinanceAddInjectionPage;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import FormFinanceAdd from '@/components/pages/finance/add/FormFinanceAdd';
|
||||||
|
|
||||||
|
const FinanceAddPage = () => {
|
||||||
|
return <FormFinanceAdd />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinanceAddPage;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import FormFinanceAddInitialBalance from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance';
|
||||||
|
|
||||||
|
const EditFinanceInitialBalancePage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const financeId = searchParams.get('financeId');
|
||||||
|
|
||||||
|
const { data: finance, isLoading: isLoadingFinance } = useSWR(
|
||||||
|
financeId,
|
||||||
|
(id: number) => FinanceApi.getSingle(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!financeId) {
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLoadingFinance && (!finance || isResponseError(finance))) {
|
||||||
|
router.replace('/404');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full p-4 flex flex-row justify-center'>
|
||||||
|
{isLoadingFinance && (
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoadingFinance && (
|
||||||
|
<FormFinanceAddInitialBalance
|
||||||
|
type='edit'
|
||||||
|
initialValues={isResponseSuccess(finance) ? finance.data : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditFinanceInitialBalancePage;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import FormFinanceInjection from '@/components/pages/finance/add/injection/FormFinanceInjection';
|
||||||
|
|
||||||
|
const EditFinanceInjectionPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const financeId = searchParams.get('financeId');
|
||||||
|
|
||||||
|
const { data: finance, isLoading: isLoadingFinance } = useSWR(
|
||||||
|
financeId,
|
||||||
|
(id: number) => FinanceApi.getSingle(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!financeId) {
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLoadingFinance && (!finance || isResponseError(finance))) {
|
||||||
|
router.replace('/404');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full p-4 flex flex-row justify-center'>
|
||||||
|
{isLoadingFinance && (
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoadingFinance && (
|
||||||
|
<FormFinanceInjection
|
||||||
|
type='edit'
|
||||||
|
initialValues={isResponseSuccess(finance) ? finance.data : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditFinanceInjectionPage;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import FormFinanceAdd from '@/components/pages/finance/add/FormFinanceAdd';
|
||||||
|
import FormFinanceAddInitialBalance from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance';
|
||||||
|
|
||||||
|
const EditFinanceTransactionPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const financeId = searchParams.get('financeId');
|
||||||
|
|
||||||
|
const { data: finance, isLoading: isLoadingFinance } = useSWR(
|
||||||
|
financeId,
|
||||||
|
(id: number) => FinanceApi.getSingle(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!financeId) {
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLoadingFinance && (!finance || isResponseError(finance))) {
|
||||||
|
router.replace('/404');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full p-4 flex flex-row justify-center'>
|
||||||
|
{isLoadingFinance && (
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoadingFinance && (
|
||||||
|
<FormFinanceAdd
|
||||||
|
type='edit'
|
||||||
|
initialValues={isResponseSuccess(finance) ? finance.data : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditFinanceTransactionPage;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import FinanceDetail from '@/components/pages/finance/FinanceDetail';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
|
||||||
|
const FinanceDetailPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const financeId = useSearchParams().get('financeId');
|
||||||
|
|
||||||
|
const { data: finance } = useSWR(financeId, () =>
|
||||||
|
FinanceApi.getSingle(Number(financeId))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!financeId) {
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(finance);
|
||||||
|
|
||||||
|
// if (!finance || isResponseError(finance)) {
|
||||||
|
// router.replace('/404');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isResponseSuccess(finance) && <FinanceDetail finance={finance.data} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinanceDetailPage;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import FinanceTable from '@/components/pages/finance/FinanceTable';
|
||||||
|
|
||||||
|
const Finance = () => {
|
||||||
|
return (
|
||||||
|
<section className='size-full p-6'>
|
||||||
|
<div className='flex flex-row gap-4'></div>
|
||||||
|
<FinanceTable />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Finance;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
|
||||||
|
|
||||||
|
const AddProductionStandardPage = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProductionStandardForm formType='add' />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddProductionStandardPage;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { ProductionStandardApi } from '@/services/api/master-data';
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
const EditProductionStandardPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
// Get Query Params
|
||||||
|
const productionStandardId = searchParams.get('productionStandardId');
|
||||||
|
|
||||||
|
// Fetch Data
|
||||||
|
const { data: productionStandard, isLoading: isLoadingProductionStandard } =
|
||||||
|
useSWR(productionStandardId, (id: number) =>
|
||||||
|
ProductionStandardApi.getSingle(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!productionStandardId) {
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isLoadingProductionStandard &&
|
||||||
|
(!productionStandard || isResponseError(productionStandard))
|
||||||
|
) {
|
||||||
|
router.replace('/404');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoadingProductionStandard && (
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
)}
|
||||||
|
{!isLoadingProductionStandard &&
|
||||||
|
isResponseSuccess(productionStandard) && (
|
||||||
|
<ProductionStandardForm
|
||||||
|
formType='edit'
|
||||||
|
initialValue={productionStandard.data}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditProductionStandardPage;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import SuspenseHelper from '@/components/helper/SuspenseHelper';
|
||||||
|
|
||||||
|
const Layout = ({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) => {
|
||||||
|
return <SuspenseHelper>{children}</SuspenseHelper>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { ProductionStandardApi } from '@/services/api/master-data';
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
const DetailProductionStandardPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
// Get Query Params
|
||||||
|
const productionStandardId = searchParams.get('productionStandardId');
|
||||||
|
|
||||||
|
// Fetch Data
|
||||||
|
const { data: productionStandard, isLoading: isLoadingProductionStandard } =
|
||||||
|
useSWR(productionStandardId, (id: number) =>
|
||||||
|
ProductionStandardApi.getSingle(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!productionStandardId) {
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isLoadingProductionStandard &&
|
||||||
|
(!productionStandard || isResponseError(productionStandard))
|
||||||
|
) {
|
||||||
|
router.replace('/404');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoadingProductionStandard && (
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
)}
|
||||||
|
{!isLoadingProductionStandard &&
|
||||||
|
isResponseSuccess(productionStandard) && (
|
||||||
|
<ProductionStandardForm
|
||||||
|
formType='detail'
|
||||||
|
initialValue={productionStandard.data}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailProductionStandardPage;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import ProductionStandardTable from '@/components/pages/master-data/production-standard/ProductionStandardTable';
|
||||||
|
|
||||||
|
const ProductionStandardPage = () => {
|
||||||
|
return (
|
||||||
|
<div className='w-full'>
|
||||||
|
<ProductionStandardTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductionStandardPage;
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
|
||||||
import ProjectFlockChickinDetail from '@/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail';
|
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
|
|
||||||
const AddChickin = () => {
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const projectFlockId = searchParams.get('projectFlockId');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<section className='w-full'>
|
|
||||||
<ProjectFlockChickinDetail projectFlockId={Number(projectFlockId)} />
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddChickin;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import ChickinTable from '@/components/pages/production/chickin/ChickinTable';
|
|
||||||
|
|
||||||
const Chickin = () => {
|
|
||||||
return (
|
|
||||||
<section className='w-full'>
|
|
||||||
<ChickinTable />
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default Chickin;
|
|
||||||
@@ -64,7 +64,7 @@ const Drawer = ({
|
|||||||
),
|
),
|
||||||
drawerSidebarContent: cn(
|
drawerSidebarContent: cn(
|
||||||
baseClassNames.drawerSidebarContent,
|
baseClassNames.drawerSidebarContent,
|
||||||
'w-full min-w-120 sm:w-fit'
|
'w-full sm:min-w-120 sm:w-fit'
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
} else if (variant === 'left') {
|
} else if (variant === 'left') {
|
||||||
@@ -76,7 +76,7 @@ const Drawer = ({
|
|||||||
),
|
),
|
||||||
drawerSidebarContent: cn(
|
drawerSidebarContent: cn(
|
||||||
baseClassNames.drawerSidebarContent,
|
baseClassNames.drawerSidebarContent,
|
||||||
'w-full min-w-120 sm:w-fit'
|
'w-full sm:min-w-120 sm:w-fit'
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import Tooltip from '@/components/Tooltip';
|
|||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
|
|
||||||
type FloatingActionsButtonProps = {
|
type FloatingActionsButtonProps = {
|
||||||
actions: {
|
actions: {
|
||||||
action: 'DETAIL' | 'EDIT' | 'DELETE';
|
action: 'DETAIL' | 'EDIT' | 'DELETE';
|
||||||
@@ -13,6 +15,7 @@ type FloatingActionsButtonProps = {
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
permissions?: string | string[];
|
||||||
}[];
|
}[];
|
||||||
approvals: {
|
approvals: {
|
||||||
action: 'APPROVED' | 'REJECTED';
|
action: 'APPROVED' | 'REJECTED';
|
||||||
@@ -20,6 +23,7 @@ type FloatingActionsButtonProps = {
|
|||||||
label?: string;
|
label?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
permissions?: string | string[];
|
||||||
}[];
|
}[];
|
||||||
selectedRowIds: number[];
|
selectedRowIds: number[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -31,6 +35,7 @@ const FloatingActionsButton = ({
|
|||||||
selectedRowIds,
|
selectedRowIds,
|
||||||
onClose,
|
onClose,
|
||||||
}: FloatingActionsButtonProps) => {
|
}: FloatingActionsButtonProps) => {
|
||||||
|
const { permissionCheck } = useAuth();
|
||||||
// Jika tidak ada baris yang dipilih, jangan tampilkan FAB
|
// Jika tidak ada baris yang dipilih, jangan tampilkan FAB
|
||||||
const positionStyles =
|
const positionStyles =
|
||||||
selectedRowIds.length > 0
|
selectedRowIds.length > 0
|
||||||
@@ -71,7 +76,18 @@ const FloatingActionsButton = ({
|
|||||||
<div className='flex gap-4 items-center'>
|
<div className='flex gap-4 items-center'>
|
||||||
{/* Render Aksi dari props.actions */}
|
{/* Render Aksi dari props.actions */}
|
||||||
{actions
|
{actions
|
||||||
.filter((action) => !action.hidden)
|
.filter((action) => {
|
||||||
|
if (action.hidden) return false;
|
||||||
|
if (action.permissions) {
|
||||||
|
if (typeof action.permissions === 'string') {
|
||||||
|
return permissionCheck(action.permissions);
|
||||||
|
}
|
||||||
|
return action.permissions.some((permission) =>
|
||||||
|
permissionCheck(permission)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.map((action, index) => {
|
.map((action, index) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -111,29 +127,41 @@ const FloatingActionsButton = ({
|
|||||||
|
|
||||||
{/* === BARIS BAWAH: Approval Buttons (Approve/Reject) === */}
|
{/* === BARIS BAWAH: Approval Buttons (Approve/Reject) === */}
|
||||||
<div className={`grid grid-cols-${approvals.length} gap-3`}>
|
<div className={`grid grid-cols-${approvals.length} gap-3`}>
|
||||||
{approvals.map((approval, index) => (
|
{approvals
|
||||||
<Button
|
.filter((approval) => {
|
||||||
key={index}
|
if (approval.permissions) {
|
||||||
onClick={approval.onClick}
|
if (typeof approval.permissions === 'string') {
|
||||||
className={cn(
|
return permissionCheck(approval.permissions);
|
||||||
'btn btn-lg w-full',
|
}
|
||||||
'bg-white/20 border-white/30',
|
return approval.permissions.some((permission) =>
|
||||||
'text-white/50 font-semibold flex items-center gap-2 rounded-lg transition-all duration-200',
|
permissionCheck(permission)
|
||||||
approval.disabled
|
);
|
||||||
? 'cursor-not-allowed'
|
}
|
||||||
: 'hover:text-white/100 hover:bg-white/40 hover:border-white/50'
|
return true;
|
||||||
)}
|
})
|
||||||
disabled={approval.disabled}
|
.map((approval, index) => (
|
||||||
>
|
<Button
|
||||||
<Icon
|
key={index}
|
||||||
icon={approval.icon}
|
onClick={approval.onClick}
|
||||||
width={20}
|
className={cn(
|
||||||
height={20}
|
'btn btn-lg w-full',
|
||||||
className={`text-${getApprovalColor(approval.action)}`}
|
'bg-white/20 border-white/30',
|
||||||
/>
|
'text-white/50 font-semibold flex items-center gap-2 rounded-lg transition-all duration-200',
|
||||||
{approval.label || approval.action}
|
approval.disabled
|
||||||
</Button>
|
? 'cursor-not-allowed'
|
||||||
))}
|
: 'hover:text-white/100 hover:bg-white/40 hover:border-white/50'
|
||||||
|
)}
|
||||||
|
disabled={approval.disabled}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={approval.icon}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className={`text-${getApprovalColor(approval.action)}`}
|
||||||
|
/>
|
||||||
|
{approval.label || approval.action}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import Drawer from '@/components/Drawer';
|
|||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import SidebarMenu from '@/components/molecules/SidebarMenu';
|
import SidebarMenu from '@/components/molecules/SidebarMenu';
|
||||||
|
import PermissionNotFound from '@/components/helper/PermissionNotFound';
|
||||||
|
|
||||||
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 { isPathActive } from '@/lib/helper';
|
||||||
|
import { ROUTE_PERMISSIONS } from '@/config/route-permission';
|
||||||
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
|
|
||||||
const MainDrawerContent = () => {
|
const MainDrawerContent = () => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
@@ -62,6 +65,11 @@ const MainDrawer = ({
|
|||||||
}>) => {
|
}>) => {
|
||||||
const { mainDrawerOpen, setMainDrawerOpen } = useUiStore();
|
const { mainDrawerOpen, setMainDrawerOpen } = useUiStore();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const { permissionCheck } = useAuth();
|
||||||
|
|
||||||
|
const isPermitted = ROUTE_PERMISSIONS[pathname]?.some((permission) =>
|
||||||
|
permissionCheck(permission)
|
||||||
|
);
|
||||||
|
|
||||||
const getPageTitle = useCallback(() => {
|
const getPageTitle = useCallback(() => {
|
||||||
let title = '';
|
let title = '';
|
||||||
@@ -101,6 +109,10 @@ const MainDrawer = ({
|
|||||||
setMainDrawerOpen(!mainDrawerOpen);
|
setMainDrawerOpen(!mainDrawerOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!isPermitted) {
|
||||||
|
return <PermissionNotFound />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
open={mainDrawerOpen}
|
open={mainDrawerOpen}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const PermissionNotFound = () => {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-screen flex flex-col justify-center items-center gap-4'>
|
||||||
|
<h2 className='text-2xl font-bold text-error'>Permission Not Found</h2>
|
||||||
|
<p className='text-gray-600 text-center'>
|
||||||
|
You do not have permission to access this page.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PermissionNotFound;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
|
|
||||||
|
interface RequirePermissionProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
permissions: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const RequirePermission = ({
|
||||||
|
children,
|
||||||
|
permissions,
|
||||||
|
}: RequirePermissionProps) => {
|
||||||
|
const { permissionCheck } = useAuth();
|
||||||
|
|
||||||
|
const isPermitted =
|
||||||
|
typeof permissions === 'string'
|
||||||
|
? permissionCheck(permissions)
|
||||||
|
: permissions.some((permission) => permissionCheck(permission));
|
||||||
|
|
||||||
|
if (!isPermitted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequirePermission;
|
||||||
@@ -2,6 +2,7 @@ import Link from 'next/link';
|
|||||||
import Menu from '@/components/menu/Menu';
|
import Menu from '@/components/menu/Menu';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { cn, isPathActive } from '@/lib/helper';
|
import { cn, isPathActive } from '@/lib/helper';
|
||||||
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
|
|
||||||
export interface SidebarMenuItem {
|
export interface SidebarMenuItem {
|
||||||
type?: 'item' | 'title';
|
type?: 'item' | 'title';
|
||||||
@@ -9,6 +10,7 @@ export interface SidebarMenuItem {
|
|||||||
link: string;
|
link: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
submenu?: SidebarMenuItem[];
|
submenu?: SidebarMenuItem[];
|
||||||
|
permission?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarMenuItemProps {
|
interface SidebarMenuItemProps {
|
||||||
@@ -22,8 +24,17 @@ interface SidebarMenuProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SidebarMenuItem = ({ item, activeLink }: SidebarMenuItemProps) => {
|
const SidebarMenuItem = ({ item, activeLink }: SidebarMenuItemProps) => {
|
||||||
|
const { permissionCheck } = useAuth();
|
||||||
const isItemActive = isPathActive(activeLink, item.link);
|
const isItemActive = isPathActive(activeLink, item.link);
|
||||||
|
|
||||||
|
const isUserPermitted = item.permission
|
||||||
|
? item.permission?.some((permissionName) => permissionCheck(permissionName))
|
||||||
|
: true;
|
||||||
|
|
||||||
|
if (!isUserPermitted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const menuItemWithoutSubmenu = (
|
const menuItemWithoutSubmenu = (
|
||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
@@ -78,13 +89,15 @@ const SidebarMenuItem = ({ item, activeLink }: SidebarMenuItemProps) => {
|
|||||||
const SidebarMenu = ({ menu, activeLink }: SidebarMenuProps) => {
|
const SidebarMenu = ({ menu, activeLink }: SidebarMenuProps) => {
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
{menu.map((menuItem, menuIdx) => (
|
{menu.map((menuItem, menuIdx) => {
|
||||||
<SidebarMenuItem
|
return (
|
||||||
key={menuIdx}
|
<SidebarMenuItem
|
||||||
item={menuItem}
|
key={menuIdx}
|
||||||
activeLink={activeLink}
|
item={menuItem}
|
||||||
/>
|
activeLink={activeLink}
|
||||||
))}
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import SelectInput, {
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
@@ -43,17 +45,18 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
{/* TODO: apply RBAC */}
|
|
||||||
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'>
|
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'>
|
||||||
<Button
|
<RequirePermission permissions='lti.closing.detail'>
|
||||||
href={`/closing/detail/?closingId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/closing/detail/?closingId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
>
|
||||||
Detail
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import toast from 'react-hot-toast';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import DropFileInput from '@/components/input/DropFileInput';
|
import DropFileInput from '@/components/input/DropFileInput';
|
||||||
|
|
||||||
@@ -62,16 +63,17 @@ const ExpenseRealizationContent = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
|
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
|
||||||
<div className='w-full sm:w-fit sm:ml-2 flex flex-row gap-2 items-center'>
|
<div className='w-full sm:w-fit sm:ml-2 flex flex-row gap-2 items-center'>
|
||||||
{/* TODO: apply RBAC */}
|
<RequirePermission permissions='lti.expense.update.realization'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='warning'
|
||||||
href={`/expense/realization/edit/?expenseId=${initialValues?.id}`}
|
href={`/expense/realization/edit/?expenseId=${initialValues?.id}`}
|
||||||
className='px-4 grow sm:grow-0'
|
className='px-4 grow sm:grow-0'
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:pencil-outline' width={24} height={24} />
|
<Icon icon='mdi:pencil-outline' width={24} height={24} />
|
||||||
Edit Realisasi
|
Edit Realisasi
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -124,36 +126,38 @@ const ExpenseRealizationContent = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2'>
|
<RequirePermission permissions='lti.expense.document.realization'>
|
||||||
<DropFileInput
|
<div className='flex flex-col gap-2'>
|
||||||
name='documents'
|
<DropFileInput
|
||||||
values={formik.values.documents}
|
name='documents'
|
||||||
onChange={realizationDocumentsChangeHandler}
|
values={formik.values.documents}
|
||||||
onDelete={realizationDocumentsDeleteHandler}
|
onChange={realizationDocumentsChangeHandler}
|
||||||
accept={{
|
onDelete={realizationDocumentsDeleteHandler}
|
||||||
...ACCEPTED_FILE_TYPE.PDF,
|
accept={{
|
||||||
...ACCEPTED_FILE_TYPE.IMAGE,
|
...ACCEPTED_FILE_TYPE.PDF,
|
||||||
}}
|
...ACCEPTED_FILE_TYPE.IMAGE,
|
||||||
maxFiles={10}
|
}}
|
||||||
className={{
|
maxFiles={10}
|
||||||
wrapper: 'mt-2',
|
className={{
|
||||||
inputWrapper: 'flex items-center',
|
wrapper: 'mt-2',
|
||||||
}}
|
inputWrapper: 'flex items-center',
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{formik.values.documents &&
|
{formik.values.documents &&
|
||||||
formik.values.documents.length > 0 && (
|
formik.values.documents.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
onClick={formik.submitForm}
|
onClick={formik.submitForm}
|
||||||
disabled={formik.isSubmitting}
|
disabled={formik.isSubmitting}
|
||||||
isLoading={formik.isSubmitting}
|
isLoading={formik.isSubmitting}
|
||||||
className='w-fit self-end'
|
className='w-fit self-end'
|
||||||
>
|
>
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
Tambah
|
Tambah
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</RequirePermission>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { useModal } from '@/components/Modal';
|
|||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
import ExpensePDFPreviewButton from '@/components/pages/expense//pdf/ExpensePDFButton';
|
import ExpensePDFPreviewButton from '@/components/pages/expense//pdf/ExpensePDFButton';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { formatCurrency, formatDate } from '@/lib/helper';
|
import { formatCurrency, formatDate } from '@/lib/helper';
|
||||||
@@ -255,100 +256,119 @@ const ExpenseRequestContent = ({
|
|||||||
|
|
||||||
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
|
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
|
||||||
{isCurrentApprovalOnManager && (
|
{isCurrentApprovalOnManager && (
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.approve.manager'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='info'
|
variant='outline'
|
||||||
onClick={approveClickHandler}
|
color='info'
|
||||||
className='w-full sm:w-fit'
|
onClick={approveClickHandler}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='lucide-lab:farm' width={24} height={24} />
|
>
|
||||||
Approve Manager
|
<Icon icon='lucide-lab:farm' width={24} height={24} />
|
||||||
</Button>
|
Approve Manager
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isCurrentApprovalOnFinance && (
|
{isCurrentApprovalOnFinance && (
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.approve.finance'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='success'
|
variant='outline'
|
||||||
onClick={approveClickHandler}
|
color='success'
|
||||||
className='w-full sm:w-fit'
|
onClick={approveClickHandler}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='tdesign:money' width={24} height={24} />
|
>
|
||||||
Approve Finance
|
<Icon icon='tdesign:money' width={24} height={24} />
|
||||||
</Button>
|
Approve Finance
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isCurrentApprovalOnRealization && (
|
{isCurrentApprovalOnRealization && (
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.complete.expense'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='success'
|
variant='outline'
|
||||||
onClick={completeExpenseClickHandler}
|
color='success'
|
||||||
className='w-full sm:w-fit'
|
onClick={completeExpenseClickHandler}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:done-all-rounded'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:done-all-rounded'
|
||||||
height={24}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
Selesai
|
/>
|
||||||
</Button>
|
Selesai
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showRejectButton && (
|
{showRejectButton && (
|
||||||
<Button
|
<RequirePermission
|
||||||
variant='outline'
|
permissions={[
|
||||||
color='error'
|
'lti.expense.approve.manager',
|
||||||
onClick={rejectClickHandler}
|
'lti.expense.approve.finance',
|
||||||
className='w-full:w-fit'
|
]}
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
<Button
|
||||||
Reject
|
variant='outline'
|
||||||
</Button>
|
color='error'
|
||||||
|
onClick={rejectClickHandler}
|
||||||
|
className='w-full:w-fit'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isExpenseCanBeRealized && (
|
{isExpenseCanBeRealized && (
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.create.realization'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='info'
|
variant='outline'
|
||||||
href={`/expense/realization/?expenseId=${initialValues?.id}`}
|
color='info'
|
||||||
className='w-full sm:w-fit'
|
href={`/expense/realization/?expenseId=${initialValues?.id}`}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:money-bag-rounded'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:money-bag-rounded'
|
||||||
height={24}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
Realisasi
|
/>
|
||||||
</Button>
|
Realisasi
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='w-full sm:w-fit sm:ml-2 flex flex-row gap-2 items-center'>
|
<div className='w-full sm:w-fit sm:ml-2 flex flex-row gap-2 items-center'>
|
||||||
{showEditButton && (
|
{showEditButton && (
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.update'>
|
||||||
type='button'
|
<Button
|
||||||
color='warning'
|
type='button'
|
||||||
href={`/expense/detail/edit/?expenseId=${initialValues?.id}`}
|
color='warning'
|
||||||
className='px-4 grow sm:grow-0'
|
href={`/expense/detail/edit/?expenseId=${initialValues?.id}`}
|
||||||
>
|
className='px-4 grow sm:grow-0'
|
||||||
<Icon icon='mdi:pencil-outline' width={24} height={24} />
|
>
|
||||||
Edit
|
<Icon icon='mdi:pencil-outline' width={24} height={24} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.delete'>
|
||||||
type='button'
|
<Button
|
||||||
color='error'
|
type='button'
|
||||||
onClick={deleteExpenseClickHandler}
|
color='error'
|
||||||
className='px-4 grow sm:grow-0'
|
onClick={deleteExpenseClickHandler}
|
||||||
>
|
className='px-4 grow sm:grow-0'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:delete-outline-rounded'
|
||||||
height={24}
|
width={24}
|
||||||
className='justify-start text-sm'
|
height={24}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Delete
|
/>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -485,36 +505,42 @@ const ExpenseRequestContent = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2'>
|
<RequirePermission permissions='lti.expense.document'>
|
||||||
<DropFileInput
|
<div className='flex flex-col gap-2'>
|
||||||
name='documents'
|
<DropFileInput
|
||||||
values={formik.values.documents}
|
name='documents'
|
||||||
onChange={requestDocumentsChangeHandler}
|
values={formik.values.documents}
|
||||||
onDelete={requestDocumentsDeleteHandler}
|
onChange={requestDocumentsChangeHandler}
|
||||||
accept={{
|
onDelete={requestDocumentsDeleteHandler}
|
||||||
...ACCEPTED_FILE_TYPE.PDF,
|
accept={{
|
||||||
...ACCEPTED_FILE_TYPE.IMAGE,
|
...ACCEPTED_FILE_TYPE.PDF,
|
||||||
}}
|
...ACCEPTED_FILE_TYPE.IMAGE,
|
||||||
maxFiles={10}
|
}}
|
||||||
className={{
|
maxFiles={10}
|
||||||
wrapper: 'mt-2',
|
className={{
|
||||||
inputWrapper: 'flex items-center',
|
wrapper: 'mt-2',
|
||||||
}}
|
inputWrapper: 'flex items-center',
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{formik.values.documents &&
|
{formik.values.documents &&
|
||||||
formik.values.documents.length > 0 && (
|
formik.values.documents.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
onClick={formik.submitForm}
|
onClick={formik.submitForm}
|
||||||
disabled={formik.isSubmitting}
|
disabled={formik.isSubmitting}
|
||||||
isLoading={formik.isSubmitting}
|
isLoading={formik.isSubmitting}
|
||||||
className='w-fit self-end'
|
className='w-fit self-end'
|
||||||
>
|
>
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
<Icon
|
||||||
Tambah
|
icon='ic:round-plus'
|
||||||
</Button>
|
width={24}
|
||||||
)}
|
height={24}
|
||||||
</div>
|
/>
|
||||||
|
Tambah
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</RequirePermission>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge';
|
|||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { ExpenseApi } from '@/services/api/expense';
|
import { ExpenseApi } from '@/services/api/expense';
|
||||||
@@ -67,58 +68,70 @@ const RowOptionsMenu = ({
|
|||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'>
|
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'>
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.detail'>
|
||||||
href={`/expense/detail/?expenseId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='primary'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{showEditButton && (
|
|
||||||
<Button
|
<Button
|
||||||
href={`/expense/detail/edit/?expenseId=${props.row.original.id}`}
|
href={`/expense/detail/?expenseId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='warning'
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
Edit
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{showEditButton && (
|
||||||
|
<RequirePermission permissions='lti.expense.update'>
|
||||||
|
<Button
|
||||||
|
href={`/expense/detail/edit/?expenseId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showRealizationButton && (
|
{showRealizationButton && (
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.create.realization'>
|
||||||
href={`/expense/realization/?expenseId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/expense/realization/?expenseId=${props.row.original.id}`}
|
||||||
color='info'
|
variant='ghost'
|
||||||
className='justify-start text-sm text-info focus-visible:text-info-content hover:text-info-content'
|
color='info'
|
||||||
>
|
className='justify-start text-sm text-info focus-visible:text-info-content hover:text-info-content'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:money-bag-rounded'
|
<Icon
|
||||||
width={16}
|
icon='material-symbols:money-bag-rounded'
|
||||||
height={16}
|
width={16}
|
||||||
/>
|
height={16}
|
||||||
Realisasi
|
/>
|
||||||
</Button>
|
Realisasi
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.delete'>
|
||||||
onClick={deleteClickHandler}
|
<Button
|
||||||
variant='ghost'
|
onClick={deleteClickHandler}
|
||||||
color='error'
|
variant='ghost'
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
color='error'
|
||||||
>
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<Icon
|
||||||
width={16}
|
icon='material-symbols:delete-outline-rounded'
|
||||||
height={16}
|
width={16}
|
||||||
className='justify-start text-sm'
|
height={16}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Delete
|
/>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
@@ -559,57 +572,70 @@ const ExpensesTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-4'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-4'>
|
||||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.create'>
|
||||||
href='/expense/add'
|
<Button
|
||||||
variant='outline'
|
href='/expense/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
{selectedRowIds.length > 0 && (
|
{selectedRowIds.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.approve.manager'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='info'
|
variant='outline'
|
||||||
onClick={bulkApproveClickHandler}
|
color='info'
|
||||||
disabled={!isAllSelectedRowLatestApprovalOnManager}
|
onClick={bulkApproveClickHandler}
|
||||||
className='w-full sm:w-fit'
|
disabled={!isAllSelectedRowLatestApprovalOnManager}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='lucide-lab:farm' width={24} height={24} />
|
>
|
||||||
Approve Manager
|
<Icon icon='lucide-lab:farm' width={24} height={24} />
|
||||||
</Button>
|
Approve Manager
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.approve.finance'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='success'
|
variant='outline'
|
||||||
onClick={bulkApproveClickHandler}
|
color='success'
|
||||||
disabled={!isAllSelectedRowLatestApprovalOnFinance}
|
onClick={bulkApproveClickHandler}
|
||||||
className='w-full sm:w-fit'
|
disabled={!isAllSelectedRowLatestApprovalOnFinance}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='tdesign:money' width={24} height={24} />
|
>
|
||||||
Approve Finance
|
<Icon icon='tdesign:money' width={24} height={24} />
|
||||||
</Button>
|
Approve Finance
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission
|
||||||
variant='outline'
|
permissions={[
|
||||||
color='error'
|
'lti.expense.approve.manager',
|
||||||
onClick={bulkRejectClickHandler}
|
'lti.expense.approve.finance',
|
||||||
disabled={
|
]}
|
||||||
!isAllSelectedRowLatestApprovalOnManager &&
|
|
||||||
!isAllSelectedRowLatestApprovalOnFinance
|
|
||||||
}
|
|
||||||
className='w-full sm:w-fit'
|
|
||||||
>
|
>
|
||||||
<Icon
|
<Button
|
||||||
icon='material-symbols:close'
|
variant='outline'
|
||||||
width={24}
|
color='error'
|
||||||
height={24}
|
onClick={bulkRejectClickHandler}
|
||||||
/>
|
disabled={
|
||||||
Reject
|
!isAllSelectedRowLatestApprovalOnManager &&
|
||||||
</Button>
|
!isAllSelectedRowLatestApprovalOnFinance
|
||||||
|
}
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:close'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import DateInput from '@/components/input/DateInput';
|
|||||||
import DropFileInput from '@/components/input/DropFileInput';
|
import DropFileInput from '@/components/input/DropFileInput';
|
||||||
import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
|
import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
|
||||||
import ExpenseRealizationKandangDetailExpense from '@/components/pages/expense/form/ExpenseRealizationKandangDetailExpense';
|
import ExpenseRealizationKandangDetailExpense from '@/components/pages/expense/form/ExpenseRealizationKandangDetailExpense';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateExpenseRealizationPayload,
|
CreateExpenseRealizationPayload,
|
||||||
@@ -290,21 +291,23 @@ const ExpenseRealizationForm = ({
|
|||||||
className={{ wrapper: 'col-span-12' }}
|
className={{ wrapper: 'col-span-12' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DropFileInput
|
<RequirePermission permissions='lti.expense.document.realization'>
|
||||||
label='Dokumen Realisasi'
|
<DropFileInput
|
||||||
name='documents'
|
label='Dokumen Realisasi'
|
||||||
values={formik.values.documents}
|
name='documents'
|
||||||
onChange={realizationDocumentsChangeHandler}
|
values={formik.values.documents}
|
||||||
onDelete={realizationDocumentsDeleteHandler}
|
onChange={realizationDocumentsChangeHandler}
|
||||||
accept={{
|
onDelete={realizationDocumentsDeleteHandler}
|
||||||
...ACCEPTED_FILE_TYPE.PDF,
|
accept={{
|
||||||
...ACCEPTED_FILE_TYPE.IMAGE,
|
...ACCEPTED_FILE_TYPE.PDF,
|
||||||
}}
|
...ACCEPTED_FILE_TYPE.IMAGE,
|
||||||
className={{
|
}}
|
||||||
wrapper: 'col-span-12',
|
className={{
|
||||||
inputWrapper: 'h-12 flex items-center',
|
wrapper: 'col-span-12',
|
||||||
}}
|
inputWrapper: 'h-12 flex items-center',
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
{formik.values.existing_documents &&
|
{formik.values.existing_documents &&
|
||||||
formik.values.existing_documents.length > 0 && (
|
formik.values.existing_documents.length > 0 && (
|
||||||
@@ -357,20 +360,22 @@ const ExpenseRealizationForm = ({
|
|||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
{type !== 'edit' && (
|
{type !== 'edit' && (
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.update'>
|
||||||
type='button'
|
<Button
|
||||||
color='warning'
|
type='button'
|
||||||
href={`/expense/detail/edit/?expenseId=${initialValues?.id}`}
|
color='warning'
|
||||||
className='px-4'
|
href={`/expense/detail/edit/?expenseId=${initialValues?.id}`}
|
||||||
>
|
className='px-4'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:edit-outline'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:edit-outline'
|
||||||
height={24}
|
width={24}
|
||||||
className='justify-start text-sm'
|
height={24}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Edit
|
/>
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import DateInput from '@/components/input/DateInput';
|
|||||||
import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
|
import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
|
||||||
import DropFileInput from '@/components/input/DropFileInput';
|
import DropFileInput from '@/components/input/DropFileInput';
|
||||||
import ExpenseRequestKandangDetailExpense from '@/components/pages/expense/form/ExpenseRequestKandangDetailExpense';
|
import ExpenseRequestKandangDetailExpense from '@/components/pages/expense/form/ExpenseRequestKandangDetailExpense';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExpenseRequestFormSchema,
|
ExpenseRequestFormSchema,
|
||||||
@@ -385,21 +386,23 @@ const ExpenseRequestForm = ({
|
|||||||
className={{ wrapper: 'col-span-12' }}
|
className={{ wrapper: 'col-span-12' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DropFileInput
|
<RequirePermission permissions='lti.expense.document'>
|
||||||
label='Dokumen Pengajuan'
|
<DropFileInput
|
||||||
name='documents'
|
label='Dokumen Pengajuan'
|
||||||
values={formik.values.documents}
|
name='documents'
|
||||||
onChange={requestDocumentsChangeHandler}
|
values={formik.values.documents}
|
||||||
onDelete={requestDocumentsDeleteHandler}
|
onChange={requestDocumentsChangeHandler}
|
||||||
accept={{
|
onDelete={requestDocumentsDeleteHandler}
|
||||||
...ACCEPTED_FILE_TYPE.PDF,
|
accept={{
|
||||||
...ACCEPTED_FILE_TYPE.IMAGE,
|
...ACCEPTED_FILE_TYPE.PDF,
|
||||||
}}
|
...ACCEPTED_FILE_TYPE.IMAGE,
|
||||||
className={{
|
}}
|
||||||
wrapper: 'col-span-12',
|
className={{
|
||||||
inputWrapper: 'h-12 flex items-center',
|
wrapper: 'col-span-12',
|
||||||
}}
|
inputWrapper: 'h-12 flex items-center',
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
{formik.values.existing_documents &&
|
{formik.values.existing_documents &&
|
||||||
formik.values.existing_documents.length > 0 && (
|
formik.values.existing_documents.length > 0 && (
|
||||||
@@ -461,36 +464,40 @@ const ExpenseRequestForm = ({
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.expense.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteExpenseClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/expense/detail/edit/?expenseId=${initialValues?.id}`}
|
onClick={deleteExpenseClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.expense.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/expense/detail/edit/?expenseId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
|
import { useModal } from '@/components/Modal';
|
||||||
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import {
|
||||||
|
FINANCE_INITIAL_BALANCE_STATUS,
|
||||||
|
FINANCE_TRANSACTION_STATUS,
|
||||||
|
} from '@/config/constant';
|
||||||
|
import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { Finance } from '@/types/api/finance/finance';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const deleteModal = useModal();
|
||||||
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
const informasiUmum = [
|
||||||
|
{
|
||||||
|
label: 'ID',
|
||||||
|
value: finance.payment_code,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Jenis Transaksi',
|
||||||
|
value: finance.transaction_type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Pihak',
|
||||||
|
value: finance.party.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tanggal',
|
||||||
|
value: formatDate(finance.payment_date, 'DD MMM yyyy'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Metode Pembayaran',
|
||||||
|
value: finance.payment_method,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Catatan',
|
||||||
|
value: finance.notes || '-',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const informasiTransfer = [
|
||||||
|
{
|
||||||
|
label: 'No. Referensi',
|
||||||
|
value: finance.reference_number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Nomor Rekening',
|
||||||
|
value: `${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Rekening ${formatTitleCase(finance.party.type)}`,
|
||||||
|
value: finance.party.account_number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Nominal',
|
||||||
|
value: formatCurrency(finance.expense_amount),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Sisa',
|
||||||
|
value: formatCurrency(finance.income_amount),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
|
await FinanceApi.delete(finance.id as number);
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
deleteModal.closeModal();
|
||||||
|
toast.success('Successfully delete Finance!');
|
||||||
|
setIsDeleteLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-6 p-6'>
|
||||||
|
<FormHeader title='' backUrl='/finance' />
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title='Detail Keuangan'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
variant='bordered'
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-2 gap-4 mb-6'>
|
||||||
|
<Table
|
||||||
|
data={informasiUmum}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: '',
|
||||||
|
id: 'label',
|
||||||
|
accessorKey: 'label',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: '',
|
||||||
|
id: 'value',
|
||||||
|
accessorKey: 'value',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
className={{
|
||||||
|
headerRowClassName: 'hidden',
|
||||||
|
paginationClassName: 'hidden',
|
||||||
|
containerClassName: 'mb-0',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
data={informasiTransfer}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: '',
|
||||||
|
id: 'label',
|
||||||
|
accessorKey: 'label',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: '',
|
||||||
|
id: 'value',
|
||||||
|
accessorKey: 'value',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
className={{
|
||||||
|
headerRowClassName: 'hidden',
|
||||||
|
paginationClassName: 'hidden',
|
||||||
|
containerClassName: 'mb-0',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-2 justify-end'>
|
||||||
|
{FINANCE_TRANSACTION_STATUS.includes(finance.transaction_type) && (
|
||||||
|
<RequirePermission permissions='lti.finance.payments.update'>
|
||||||
|
<Button
|
||||||
|
color='warning'
|
||||||
|
className='min-w-24'
|
||||||
|
href={`/finance/detail/edit?financeId=${finance.id}`}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:pencil-outline' />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
{FINANCE_INITIAL_BALANCE_STATUS.includes(finance.transaction_type) && (
|
||||||
|
<RequirePermission permissions='lti.finance.initial_balances.update'>
|
||||||
|
<Button
|
||||||
|
color='warning'
|
||||||
|
className='min-w-24'
|
||||||
|
href={`/finance/detail/edit/initial-balance?financeId=${finance.id}`}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:pencil-outline' />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
<RequirePermission permissions='lti.finance.transaction.delete'>
|
||||||
|
<Button
|
||||||
|
color='error'
|
||||||
|
className='min-w-24'
|
||||||
|
onClick={() => deleteModal.openModal()}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:delete-outline' />
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</div>
|
||||||
|
<ConfirmationModal
|
||||||
|
ref={deleteModal.ref}
|
||||||
|
type='error'
|
||||||
|
text={`Apakah anda yakin ingin menghapus data Finance ini (${finance?.payment_code})?`}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Tidak',
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: 'error',
|
||||||
|
isLoading: isDeleteLoading,
|
||||||
|
onClick: confirmationModalDeleteClickHandler,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinanceDetail;
|
||||||
@@ -0,0 +1,564 @@
|
|||||||
|
import { ChangeEventHandler, useMemo, useState } from 'react';
|
||||||
|
import { CellContext, Row } from '@tanstack/react-table';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import Dropdown from '@/components/dropdown/Dropdown';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
|
import Menu from '@/components/menu/Menu';
|
||||||
|
import MenuItem from '@/components/menu/MenuItem';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import Tooltip from '@/components/Tooltip';
|
||||||
|
import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
|
||||||
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
|
import { Finance } from '@/types/api/finance/finance';
|
||||||
|
import {
|
||||||
|
FINANCE_INITIAL_BALANCE_STATUS,
|
||||||
|
FINANCE_INJECTION_STATUS,
|
||||||
|
FINANCE_TRANSACTION_STATUS,
|
||||||
|
ROWS_OPTIONS,
|
||||||
|
} from '@/config/constant';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data';
|
||||||
|
import { Bank } from '@/types/api/master-data/bank';
|
||||||
|
import { useModal } from '@/components/Modal';
|
||||||
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
|
|
||||||
|
const RowOptionsMenu = ({
|
||||||
|
type = 'dropdown',
|
||||||
|
props,
|
||||||
|
deleteClickHandler,
|
||||||
|
}: {
|
||||||
|
type: 'dropdown' | 'collapse';
|
||||||
|
props: CellContext<Finance, unknown>;
|
||||||
|
deleteClickHandler: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<RowOptionsMenuWrapper type={type}>
|
||||||
|
<RequirePermission permissions='lti.finance.transaction.detail'>
|
||||||
|
<Button
|
||||||
|
href={`/finance/detail?financeId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='primary'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{FINANCE_TRANSACTION_STATUS.includes(
|
||||||
|
props.row.original.transaction_type
|
||||||
|
) && (
|
||||||
|
<RequirePermission permissions='lti.finance.payments.update'>
|
||||||
|
<Button
|
||||||
|
href={`/finance/detail/edit?financeId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{FINANCE_INITIAL_BALANCE_STATUS.includes(
|
||||||
|
props.row.original.transaction_type
|
||||||
|
) && (
|
||||||
|
<RequirePermission permissions='lti.finance.initial_balances.update'>
|
||||||
|
<Button
|
||||||
|
href={`/finance/detail/edit/initial-balance?financeId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{FINANCE_INJECTION_STATUS.includes(
|
||||||
|
props.row.original.transaction_type
|
||||||
|
) && (
|
||||||
|
<RequirePermission permissions='lti.finance.injections.update'>
|
||||||
|
<Button
|
||||||
|
href={`/finance/detail/edit/injection?financeId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.finance.transaction.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='text-error hover:text-inherit'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</RowOptionsMenuWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FinanceTable = () => {
|
||||||
|
const {
|
||||||
|
state: tableFilterState,
|
||||||
|
updateFilter,
|
||||||
|
setPage,
|
||||||
|
setPageSize,
|
||||||
|
toQueryString: getTableFilterQueryString,
|
||||||
|
} = useTableFilter({
|
||||||
|
initial: {
|
||||||
|
search: '',
|
||||||
|
transactionType: '',
|
||||||
|
bankId: '',
|
||||||
|
partyType: '',
|
||||||
|
sortBy: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
},
|
||||||
|
paramMap: {
|
||||||
|
page: 'page',
|
||||||
|
pageSize: 'limit',
|
||||||
|
transactionType: 'transaction_type',
|
||||||
|
bankId: 'bank_id',
|
||||||
|
partyType: 'party_type',
|
||||||
|
sortBy: 'sort_date',
|
||||||
|
startDate: 'start_date',
|
||||||
|
endDate: 'end_date',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== State =====
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const deleteModal = useModal();
|
||||||
|
const [pendingFilters, setPendingFilters] = useState({
|
||||||
|
search: '',
|
||||||
|
transactionType: '',
|
||||||
|
bankId: '',
|
||||||
|
partyType: '',
|
||||||
|
sortBy: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
});
|
||||||
|
const [selectedTransactionType, setSelectedTransactionType] =
|
||||||
|
useState<OptionType | null>(null);
|
||||||
|
const [selectedBank, setSelectedBank] = useState<OptionType | null>(null);
|
||||||
|
const [selectedPartyType, setSelectedPartyType] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [selectedSortBy, setSelectedSortBy] = useState<OptionType | null>(null);
|
||||||
|
const [selectedFinance, setSelectedFinance] = useState<Finance | null>(null);
|
||||||
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
|
// ===== Fetch Data =====
|
||||||
|
const {
|
||||||
|
data: finances,
|
||||||
|
isLoading,
|
||||||
|
mutate: refreshFinances,
|
||||||
|
} = useSWR(
|
||||||
|
`${FinanceApi.basePath}/transactions${getTableFilterQueryString()}`,
|
||||||
|
FinanceApi.getAllFetcher
|
||||||
|
);
|
||||||
|
|
||||||
|
// ===== Options =====
|
||||||
|
const transactionTypeOptions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ label: 'Transfer', value: 'TRANSFER' },
|
||||||
|
{ label: 'Cash', value: 'CASH' },
|
||||||
|
{ label: 'Card', value: 'CARD' },
|
||||||
|
{ label: 'Cheque', value: 'CHEQUE' },
|
||||||
|
{ label: 'Saldo', value: 'SALDO' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
const partyTypeOptions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ label: 'Customer', value: 'CUSTOMER' },
|
||||||
|
{ label: 'Supplier', value: 'SUPPLIER' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
const sortByOptions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ label: 'Tanggal Pembayaran', value: 'payment_date' },
|
||||||
|
{ label: 'Tanggal Dibuat', value: 'created_at' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
const { options: bankOptions, rawData: bankRawData } = useSelect<Bank>(
|
||||||
|
BankApi.basePath,
|
||||||
|
'id',
|
||||||
|
'alias',
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
limit: 'limit',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ===== Handler =====
|
||||||
|
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
|
setPendingFilters((prev) => ({ ...prev, search: e.target.value }));
|
||||||
|
};
|
||||||
|
const transactionTypeChangeHandler = (
|
||||||
|
val: OptionType | OptionType[] | null
|
||||||
|
) => {
|
||||||
|
setSelectedTransactionType(val as OptionType);
|
||||||
|
setPendingFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
transactionType: val ? ((val as OptionType).value as string) : '',
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
const bankChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
setSelectedBank(val as OptionType);
|
||||||
|
setPendingFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
bankId: val ? ((val as OptionType).value as string) : '',
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
const partyTypeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
setSelectedPartyType(val as OptionType);
|
||||||
|
setPendingFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
partyType: val ? ((val as OptionType).value as string) : '',
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
const sortByChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
setSelectedSortBy(val as OptionType);
|
||||||
|
setPendingFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
sortBy: val ? ((val as OptionType).value as string) : '',
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
const startDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
|
setPendingFilters((prev) => ({ ...prev, startDate: e.target.value }));
|
||||||
|
};
|
||||||
|
const endDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
|
setPendingFilters((prev) => ({ ...prev, endDate: e.target.value }));
|
||||||
|
};
|
||||||
|
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
const newVal = val as OptionType;
|
||||||
|
setPageSize(newVal.value as number);
|
||||||
|
};
|
||||||
|
const submitFilterHandler = () => {
|
||||||
|
updateFilter('search', pendingFilters.search);
|
||||||
|
updateFilter('transactionType', pendingFilters.transactionType);
|
||||||
|
updateFilter('bankId', pendingFilters.bankId);
|
||||||
|
updateFilter('partyType', pendingFilters.partyType);
|
||||||
|
updateFilter('sortBy', pendingFilters.sortBy);
|
||||||
|
updateFilter('startDate', pendingFilters.startDate);
|
||||||
|
updateFilter('endDate', pendingFilters.endDate);
|
||||||
|
};
|
||||||
|
const resetFilterHandler = () => {
|
||||||
|
setSelectedTransactionType(null);
|
||||||
|
setSelectedBank(null);
|
||||||
|
setSelectedPartyType(null);
|
||||||
|
setSelectedSortBy(null);
|
||||||
|
|
||||||
|
const emptyFilters = {
|
||||||
|
search: '',
|
||||||
|
transactionType: '',
|
||||||
|
bankId: '',
|
||||||
|
partyType: '',
|
||||||
|
sortBy: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
};
|
||||||
|
setPendingFilters(emptyFilters);
|
||||||
|
|
||||||
|
updateFilter('search', '');
|
||||||
|
updateFilter('transactionType', '');
|
||||||
|
updateFilter('bankId', '');
|
||||||
|
updateFilter('partyType', '');
|
||||||
|
updateFilter('sortBy', '');
|
||||||
|
updateFilter('startDate', '');
|
||||||
|
updateFilter('endDate', '');
|
||||||
|
};
|
||||||
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
|
await FinanceApi.delete(selectedFinance?.id as number);
|
||||||
|
refreshFinances();
|
||||||
|
|
||||||
|
deleteModal.closeModal();
|
||||||
|
toast.success('Successfully delete Finance!');
|
||||||
|
setIsDeleteLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
header: 'ID',
|
||||||
|
accessorKey: 'payment_code',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'References Number',
|
||||||
|
accessorKey: 'reference_number',
|
||||||
|
cell: (props: CellContext<Finance, unknown>) => {
|
||||||
|
const value = props.row.original.reference_number;
|
||||||
|
return <span>{value ?? '-'}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Jenis Transaksi',
|
||||||
|
accessorKey: 'transaction_type',
|
||||||
|
cell: (props: CellContext<Finance, unknown>) => {
|
||||||
|
const value = props.row.original.transaction_type
|
||||||
|
.split('_')
|
||||||
|
.join(' ');
|
||||||
|
return <span>{formatTitleCase(value)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Pihak',
|
||||||
|
accessorFn: (finance: Finance) => finance.party.name,
|
||||||
|
cell: (props: CellContext<Finance, unknown>) => {
|
||||||
|
if (props.row.original.party.id) {
|
||||||
|
return <span>{props.row.original.party.name}</span>;
|
||||||
|
}
|
||||||
|
return <span>{'-'}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Tanggal',
|
||||||
|
accessorFn: (finance: Finance) =>
|
||||||
|
formatDate(finance.payment_date, 'DD MMM YYYY'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Metode Pembayaran',
|
||||||
|
accessorKey: 'payment_method',
|
||||||
|
cell: (props: CellContext<Finance, unknown>) => {
|
||||||
|
const value = props.row.original.payment_method.split('_').join(' ');
|
||||||
|
return <span>{formatTitleCase(value)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Bank',
|
||||||
|
accessorFn: (finance: Finance) =>
|
||||||
|
`${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Pengeluaran (Rp)',
|
||||||
|
accessorFn: (finance: Finance) =>
|
||||||
|
formatCurrency(finance.expense_amount),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Pemasukan (Rp)',
|
||||||
|
accessorFn: (finance: Finance) => formatCurrency(finance.income_amount),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Aksi',
|
||||||
|
cell: (props: CellContext<Finance, unknown>) => {
|
||||||
|
const currentPageSize =
|
||||||
|
props.table.getPaginationRowModel().rows.length;
|
||||||
|
const currentPageRows = props.table.getPaginationRowModel().flatRows;
|
||||||
|
const currentRowRelativeIndex =
|
||||||
|
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||||
|
|
||||||
|
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
||||||
|
|
||||||
|
const deleteClickHandler = () => {
|
||||||
|
setSelectedFinance(props.row.original);
|
||||||
|
deleteModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{currentPageSize > 2 && (
|
||||||
|
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||||
|
<RowOptionsMenu
|
||||||
|
type='dropdown'
|
||||||
|
props={props}
|
||||||
|
deleteClickHandler={deleteClickHandler}
|
||||||
|
/>
|
||||||
|
</RowDropdownOptions>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentPageSize <= 2 && (
|
||||||
|
<RowCollapseOptions>
|
||||||
|
<RowOptionsMenu
|
||||||
|
type='collapse'
|
||||||
|
props={props}
|
||||||
|
deleteClickHandler={deleteClickHandler}
|
||||||
|
/>
|
||||||
|
</RowCollapseOptions>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<section className='size-full p-6 flex flex-col gap-6'>
|
||||||
|
<div className='flex justify-end gap-2'>
|
||||||
|
<RequirePermission permissions='lti.finance.injections.create'>
|
||||||
|
<Button
|
||||||
|
color='warning'
|
||||||
|
className='min-w-24'
|
||||||
|
href='/finance/add/injection'
|
||||||
|
>
|
||||||
|
Injection Saldo Bank
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
<RequirePermission permissions='lti.finance.initial_balances.create'>
|
||||||
|
<Button
|
||||||
|
color='info'
|
||||||
|
className='text-white min-w-24'
|
||||||
|
href='/finance/add/initial-balance'
|
||||||
|
>
|
||||||
|
Saldo Awal
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
<RequirePermission permissions='lti.finance.payments.create'>
|
||||||
|
<Button color='primary' className='min-w-24' href='/finance/add'>
|
||||||
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</div>
|
||||||
|
<Card
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
footer={
|
||||||
|
<div className='flex justify-end gap-2'>
|
||||||
|
<Button
|
||||||
|
color='warning'
|
||||||
|
className='min-w-24'
|
||||||
|
onClick={resetFilterHandler}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color='primary'
|
||||||
|
className='min-w-24'
|
||||||
|
onClick={submitFilterHandler}
|
||||||
|
>
|
||||||
|
Cari
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-4 gap-6'>
|
||||||
|
<SelectInput
|
||||||
|
options={transactionTypeOptions}
|
||||||
|
label='Jenis Transaksi'
|
||||||
|
value={selectedTransactionType}
|
||||||
|
onChange={transactionTypeChangeHandler}
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
options={
|
||||||
|
isResponseSuccess(bankRawData)
|
||||||
|
? bankOptions.map((bank) => ({
|
||||||
|
label:
|
||||||
|
bankRawData.data.find((data) => data.id === bank.value)
|
||||||
|
?.alias +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data.find((data) => data.id === bank.value)
|
||||||
|
?.account_number +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data.find((data) => data.id === bank.value)
|
||||||
|
?.owner,
|
||||||
|
value: bank.value,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
label='Bank'
|
||||||
|
value={selectedBank}
|
||||||
|
onChange={bankChangeHandler}
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
options={partyTypeOptions}
|
||||||
|
label='Pihak'
|
||||||
|
value={selectedPartyType}
|
||||||
|
onChange={partyTypeChangeHandler}
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<DebouncedTextInput
|
||||||
|
name='search'
|
||||||
|
label='Cari'
|
||||||
|
placeholder='Cari'
|
||||||
|
value={pendingFilters.search}
|
||||||
|
onChange={searchChangeHandler}
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
options={sortByOptions}
|
||||||
|
label='Urutkan Berdasarkan'
|
||||||
|
value={selectedSortBy}
|
||||||
|
onChange={sortByChangeHandler}
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
name='startDate'
|
||||||
|
label='Periode Tanggal (Mulai)'
|
||||||
|
value={pendingFilters.startDate}
|
||||||
|
onChange={startDateChangeHandler}
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
name='endDate'
|
||||||
|
label='Periode Tanggal (Akhir)'
|
||||||
|
value={pendingFilters.endDate}
|
||||||
|
onChange={endDateChangeHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Table<Finance>
|
||||||
|
data={isResponseSuccess(finances) ? finances.data : []}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={tableFilterState.pageSize}
|
||||||
|
page={tableFilterState.page}
|
||||||
|
onPageChange={setPage}
|
||||||
|
onPageSizeChange={setPageSize}
|
||||||
|
totalItems={
|
||||||
|
isResponseSuccess(finances) ? finances.meta?.total_results : 0
|
||||||
|
}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
<ConfirmationModal
|
||||||
|
ref={deleteModal.ref}
|
||||||
|
type='error'
|
||||||
|
text={`Apakah anda yakin ingin menghapus data Finance ini (${selectedFinance?.payment_code})?`}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Tidak',
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: 'error',
|
||||||
|
isLoading: isDeleteLoading,
|
||||||
|
onClick: confirmationModalDeleteClickHandler,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinanceTable;
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Payload format:
|
||||||
|
* {
|
||||||
|
"party_id": 1,
|
||||||
|
"party_type": "CUSTOMER",
|
||||||
|
"payment_date": "2025-11-21",
|
||||||
|
"payment_method": "Transfer",
|
||||||
|
"bank_id": 1,
|
||||||
|
"reference_number": "DO.MBU.123",
|
||||||
|
"nominal": 25000000,
|
||||||
|
"notes": "Pembayaran piutang penjualan telur"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Type for form values (includes option objects for SelectInput)
|
||||||
|
export type FinanceFormValues = {
|
||||||
|
party_type_option: OptionType | null;
|
||||||
|
party_id_option: OptionType | null;
|
||||||
|
party_account_number: string;
|
||||||
|
payment_date: string;
|
||||||
|
payment_method_option: OptionType | null;
|
||||||
|
bank_id_option: OptionType | null;
|
||||||
|
reference_number: string;
|
||||||
|
nominal: string;
|
||||||
|
notes: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FinanceFormSchema = Yup.object().shape({
|
||||||
|
party_type_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Jenis transaksi wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
party_id_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Pihak wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
party_account_number: Yup.string().required('Nomor rekening wajib diisi'),
|
||||||
|
payment_date: Yup.string().required('Tanggal pembayaran wajib diisi'),
|
||||||
|
payment_method_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Metode pembayaran wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
bank_id_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Bank wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
reference_number: Yup.string().required('Nomor referensi wajib diisi'),
|
||||||
|
nominal: Yup.string().required('Nominal wajib diisi'),
|
||||||
|
notes: Yup.string().required('Catatan wajib diisi'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateFinanceFormSchema = FinanceFormSchema;
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
|
import TextArea from '@/components/input/TextArea';
|
||||||
|
import TextInput from '@/components/input/TextInput';
|
||||||
|
import {
|
||||||
|
FinanceFormSchema,
|
||||||
|
FinanceFormValues,
|
||||||
|
} from '@/components/pages/finance/add/FormFinanceAdd.schema';
|
||||||
|
import {
|
||||||
|
FINANCE_PARTY_TYPE_OPTIONS,
|
||||||
|
FINANCE_PAYMENT_METHOD_OPTIONS,
|
||||||
|
} from '@/config/constant';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { formatDate, formatTitleCase } from '@/lib/helper';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data';
|
||||||
|
import {
|
||||||
|
CreateFinancePayment,
|
||||||
|
Finance,
|
||||||
|
UpdateFinancePayment,
|
||||||
|
} from '@/types/api/finance/finance';
|
||||||
|
import { Bank } from '@/types/api/master-data/bank';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
interface FormFinanceAddProps {
|
||||||
|
type?: 'add' | 'edit';
|
||||||
|
initialValues?: Finance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormFinanceAdd = ({
|
||||||
|
type = 'add',
|
||||||
|
initialValues,
|
||||||
|
}: FormFinanceAddProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// ===== Formik =====
|
||||||
|
const formikInitialValues = useMemo((): FinanceFormValues => {
|
||||||
|
return {
|
||||||
|
party_type_option:
|
||||||
|
FINANCE_PARTY_TYPE_OPTIONS.find(
|
||||||
|
(option) => option.value === initialValues?.party.type
|
||||||
|
) || null,
|
||||||
|
party_id_option: {
|
||||||
|
label: initialValues?.party.name || '',
|
||||||
|
value: initialValues?.party.id || 0,
|
||||||
|
},
|
||||||
|
payment_date: initialValues?.payment_date || '',
|
||||||
|
payment_method_option:
|
||||||
|
FINANCE_PAYMENT_METHOD_OPTIONS.find(
|
||||||
|
(option) => option.value === initialValues?.payment_method
|
||||||
|
) || null,
|
||||||
|
bank_id_option: initialValues?.bank
|
||||||
|
? {
|
||||||
|
label: initialValues.bank.name,
|
||||||
|
value: initialValues.bank.id,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
party_account_number: initialValues?.party.account_number || '',
|
||||||
|
reference_number: initialValues?.reference_number || '',
|
||||||
|
nominal: initialValues?.nominal.toString() || '',
|
||||||
|
notes: initialValues?.notes || '',
|
||||||
|
};
|
||||||
|
}, [initialValues]);
|
||||||
|
|
||||||
|
const formik = useFormik<FinanceFormValues>({
|
||||||
|
initialValues: formikInitialValues,
|
||||||
|
validationSchema: FinanceFormSchema,
|
||||||
|
validateOnChange: true,
|
||||||
|
validateOnBlur: true,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
const payload = transformFormValuesToPayload(values);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'add':
|
||||||
|
await createFinance(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit':
|
||||||
|
if (initialValues?.id) {
|
||||||
|
await updateFinance(initialValues.id, payload);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== Options =====
|
||||||
|
const { options: partyOptions, isLoadingOptions: isLoadingPartyOptions } =
|
||||||
|
useSelect(
|
||||||
|
formik.values.party_type_option?.value === 'CUSTOMER'
|
||||||
|
? CustomerApi.basePath
|
||||||
|
: SupplierApi.basePath,
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'',
|
||||||
|
{ limit: 'limit' }
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
options: bankOptions,
|
||||||
|
rawData: bankRawData,
|
||||||
|
isLoadingOptions: isLoadingBankOptions,
|
||||||
|
} = useSelect<Bank>(BankApi.basePath, 'id', 'name', '', { limit: 'limit' });
|
||||||
|
|
||||||
|
// ===== Helper Functions =====
|
||||||
|
const transformFormValuesToPayload = (
|
||||||
|
values: FinanceFormValues
|
||||||
|
): CreateFinancePayment => {
|
||||||
|
return {
|
||||||
|
party_id: Number(values.party_id_option?.value) || 0,
|
||||||
|
party_type: (values.party_type_option?.value as string) || '',
|
||||||
|
payment_date: formatDate(values.payment_date, 'YYYY-MM-DD'),
|
||||||
|
payment_method: (values.payment_method_option?.value as string) || '',
|
||||||
|
bank_id: Number(values.bank_id_option?.value) || 0,
|
||||||
|
reference_number: values.reference_number,
|
||||||
|
nominal: Number(values.nominal.replace(/\D/g, '')) || 0,
|
||||||
|
notes: values.notes,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== Handler =====
|
||||||
|
const createFinance = useCallback(
|
||||||
|
async (payload: CreateFinancePayment) => {
|
||||||
|
const response = await FinanceApi.create(payload);
|
||||||
|
|
||||||
|
if (isResponseError(response)) {
|
||||||
|
toast.error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Data berhasil ditambahkan');
|
||||||
|
router.refresh();
|
||||||
|
router.push('/finance');
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
const updateFinance = useCallback(
|
||||||
|
async (financeId: number, payload: UpdateFinancePayment) => {
|
||||||
|
const response = await FinanceApi.update(financeId, payload);
|
||||||
|
|
||||||
|
if (isResponseError(response)) {
|
||||||
|
toast.error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Data berhasil diperbarui');
|
||||||
|
router.refresh();
|
||||||
|
router.push('/finance');
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className='w-full max-w-xl mx-auto'>
|
||||||
|
<div className='flex flex-col gap-6 p-6'>
|
||||||
|
<FormHeader
|
||||||
|
title={`${type === 'add' ? 'Tambah' : 'Ubah'} Data Keuangan`}
|
||||||
|
backUrl='/finance'
|
||||||
|
/>
|
||||||
|
<form className='flex flex-col gap-4' onSubmit={formik.handleSubmit}>
|
||||||
|
<SelectInput
|
||||||
|
label='Jenis Transaksi'
|
||||||
|
placeholder='Pilih jenis transaksi'
|
||||||
|
options={FINANCE_PARTY_TYPE_OPTIONS}
|
||||||
|
value={formik.values.party_type_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('party_type_option', value);
|
||||||
|
}}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.party_type_option &&
|
||||||
|
formik.errors.party_type_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.party_type_option &&
|
||||||
|
formik.errors.party_type_option
|
||||||
|
? formik.errors.party_type_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
label={
|
||||||
|
formik.values.party_type_option?.value
|
||||||
|
? formatTitleCase(
|
||||||
|
formik.values.party_type_option.value as string
|
||||||
|
)
|
||||||
|
: 'Pilih Jenis Transaksi Dahulu'
|
||||||
|
}
|
||||||
|
placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis transaksi dahulu'}`}
|
||||||
|
options={partyOptions}
|
||||||
|
value={formik.values.party_id_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('party_id_option', value);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingPartyOptions}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.party_id_option && formik.errors.party_id_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.party_id_option && formik.errors.party_id_option
|
||||||
|
? formik.errors.party_id_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
isDisabled={!formik.values.party_type_option?.value}
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
label='Tanggal'
|
||||||
|
placeholder='Pilih tanggal'
|
||||||
|
name='payment_date'
|
||||||
|
value={formik.values.payment_date}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.payment_date && formik.errors.payment_date
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.payment_date && formik.errors.payment_date
|
||||||
|
? formik.errors.payment_date
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
label='Metode Pembayaran'
|
||||||
|
placeholder='Pilih metode pembayaran'
|
||||||
|
options={FINANCE_PAYMENT_METHOD_OPTIONS}
|
||||||
|
value={formik.values.payment_method_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('payment_method_option', value);
|
||||||
|
}}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.payment_method_option &&
|
||||||
|
formik.errors.payment_method_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.payment_method_option &&
|
||||||
|
formik.errors.payment_method_option
|
||||||
|
? formik.errors.payment_method_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
label='Bank'
|
||||||
|
placeholder='Pilih bank'
|
||||||
|
options={
|
||||||
|
isResponseSuccess(bankRawData)
|
||||||
|
? bankOptions.map((option) => ({
|
||||||
|
label:
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.alias +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.account_number +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.owner,
|
||||||
|
value: option.value,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
value={formik.values.bank_id_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('bank_id_option', value);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingBankOptions}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.bank_id_option && formik.errors.bank_id_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.bank_id_option && formik.errors.bank_id_option
|
||||||
|
? formik.errors.bank_id_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={`Nomor Rekening ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'Pihak'}`}
|
||||||
|
placeholder={`Masukkan nomor rekening ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'pihak'}`}
|
||||||
|
name='party_account_number'
|
||||||
|
value={formik.values.party_account_number}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.party_account_number &&
|
||||||
|
formik.errors.party_account_number
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.party_account_number &&
|
||||||
|
formik.errors.party_account_number
|
||||||
|
? formik.errors.party_account_number
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label='Nomor Referensi'
|
||||||
|
placeholder='Masukkan nomor referensi'
|
||||||
|
name='reference_number'
|
||||||
|
value={formik.values.reference_number}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.reference_number &&
|
||||||
|
formik.errors.reference_number
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.reference_number &&
|
||||||
|
formik.errors.reference_number
|
||||||
|
? formik.errors.reference_number
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label='Nominal'
|
||||||
|
placeholder='Masukkan nominal'
|
||||||
|
name='nominal'
|
||||||
|
value={formik.values.nominal}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(formik.touched.nominal && formik.errors.nominal)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.nominal && formik.errors.nominal
|
||||||
|
? formik.errors.nominal
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextArea
|
||||||
|
label='Catatan'
|
||||||
|
placeholder='Masukkan catatan'
|
||||||
|
name='notes'
|
||||||
|
value={formik.values.notes}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(formik.touched.notes && formik.errors.notes)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.notes && formik.errors.notes
|
||||||
|
? formik.errors.notes
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div className='flex justify-center gap-4'>
|
||||||
|
<Button
|
||||||
|
type='reset'
|
||||||
|
color='warning'
|
||||||
|
className='w-min-24'
|
||||||
|
onClick={() => formik.resetForm()}
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
className='w-min-24'
|
||||||
|
disabled={formik.isSubmitting || !formik.isValid}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormFinanceAdd;
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Payload format for Initial Balance:
|
||||||
|
* {
|
||||||
|
"party_type": "CUSTOMER",
|
||||||
|
"party_id": 1,
|
||||||
|
"bank_id": 1,
|
||||||
|
"reference_number": "IB.MBU.001",
|
||||||
|
"initial_balance_type": "DEBIT",
|
||||||
|
"nominal": 5000000,
|
||||||
|
"note": "Saldo awal piutang customer"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Type for form values (includes option objects for SelectInput)
|
||||||
|
export type InitialBalanceFormValues = {
|
||||||
|
party_type_option: OptionType | null;
|
||||||
|
party_id_option: OptionType | null;
|
||||||
|
bank_id_option: OptionType | null;
|
||||||
|
reference_number: string;
|
||||||
|
initial_balance_type_option: OptionType | null;
|
||||||
|
nominal: string;
|
||||||
|
note: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InitialBalanceFormSchema = Yup.object().shape({
|
||||||
|
party_type_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Jenis pihak wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
party_id_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Pihak wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
bank_id_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Bank wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
reference_number: Yup.string().required('Nomor referensi wajib diisi'),
|
||||||
|
initial_balance_type_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Tipe saldo awal wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
nominal: Yup.string().required('Nominal wajib diisi'),
|
||||||
|
note: Yup.string().required('Catatan wajib diisi'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateInitialBalanceFormSchema = InitialBalanceFormSchema;
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||||
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
|
import TextArea from '@/components/input/TextArea';
|
||||||
|
import TextInput from '@/components/input/TextInput';
|
||||||
|
import {
|
||||||
|
InitialBalanceFormSchema,
|
||||||
|
InitialBalanceFormValues,
|
||||||
|
} from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.schema';
|
||||||
|
import {
|
||||||
|
FINANCE_INITIAL_BALANCE_TYPE_OPTIONS,
|
||||||
|
FINANCE_PARTY_TYPE_OPTIONS,
|
||||||
|
} from '@/config/constant';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { formatTitleCase } from '@/lib/helper';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data';
|
||||||
|
import {
|
||||||
|
CreateInitialBalance,
|
||||||
|
Finance,
|
||||||
|
UpdateInitialBalance,
|
||||||
|
} from '@/types/api/finance/finance';
|
||||||
|
import { Bank } from '@/types/api/master-data/bank';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
interface FormFinanceAddInitialBalanceProps {
|
||||||
|
type?: 'add' | 'edit';
|
||||||
|
initialValues?: Finance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormFinanceAddInitialBalance = ({
|
||||||
|
type = 'add',
|
||||||
|
initialValues,
|
||||||
|
}: FormFinanceAddInitialBalanceProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// ===== Formik =====
|
||||||
|
const formikInitialValues = useMemo((): InitialBalanceFormValues => {
|
||||||
|
// Type assertion to handle potential initial_balance_type field
|
||||||
|
const extendedInitialValues = initialValues as Finance & {
|
||||||
|
initial_balance_type?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
party_type_option:
|
||||||
|
FINANCE_PARTY_TYPE_OPTIONS.find(
|
||||||
|
(option) => option.value === initialValues?.party.type
|
||||||
|
) || null,
|
||||||
|
party_id_option: initialValues?.party
|
||||||
|
? {
|
||||||
|
label: initialValues.party.name,
|
||||||
|
value: initialValues.party.id,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
bank_id_option: initialValues?.bank
|
||||||
|
? {
|
||||||
|
label: initialValues.bank.name,
|
||||||
|
value: initialValues.bank.id,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
reference_number: initialValues?.reference_number || '',
|
||||||
|
initial_balance_type_option:
|
||||||
|
(initialValues?.nominal ?? 0) < 0
|
||||||
|
? FINANCE_INITIAL_BALANCE_TYPE_OPTIONS.find(
|
||||||
|
(option) => option.value === 'NEGATIVE'
|
||||||
|
) || null
|
||||||
|
: FINANCE_INITIAL_BALANCE_TYPE_OPTIONS.find(
|
||||||
|
(option) => option.value === 'POSITIVE'
|
||||||
|
) || null,
|
||||||
|
nominal: initialValues?.nominal?.toString() || '',
|
||||||
|
note: initialValues?.notes || '',
|
||||||
|
};
|
||||||
|
}, [initialValues]);
|
||||||
|
|
||||||
|
const formik = useFormik<InitialBalanceFormValues>({
|
||||||
|
initialValues: formikInitialValues,
|
||||||
|
validationSchema: InitialBalanceFormSchema,
|
||||||
|
validateOnChange: true,
|
||||||
|
validateOnBlur: true,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
const payload = transformFormValuesToPayload(values);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'add':
|
||||||
|
await createInitialBalance(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit':
|
||||||
|
if (initialValues?.id) {
|
||||||
|
await updateInitialBalance(initialValues.id, payload);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== Options =====
|
||||||
|
const { options: partyOptions, isLoadingOptions: isLoadingPartyOptions } =
|
||||||
|
useSelect(
|
||||||
|
formik.values.party_type_option?.value === 'CUSTOMER'
|
||||||
|
? CustomerApi.basePath
|
||||||
|
: SupplierApi.basePath,
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'',
|
||||||
|
{ limit: 'limit' }
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
options: bankOptions,
|
||||||
|
rawData: bankRawData,
|
||||||
|
isLoadingOptions: isLoadingBankOptions,
|
||||||
|
} = useSelect<Bank>(BankApi.basePath, 'id', 'name', '', { limit: 'limit' });
|
||||||
|
|
||||||
|
// ===== Helper Functions =====
|
||||||
|
const transformFormValuesToPayload = (
|
||||||
|
values: InitialBalanceFormValues
|
||||||
|
): CreateInitialBalance => {
|
||||||
|
return {
|
||||||
|
party_type: (values.party_type_option?.value as string) || '',
|
||||||
|
party_id: Number(values.party_id_option?.value) || 0,
|
||||||
|
bank_id: Number(values.bank_id_option?.value) || 0,
|
||||||
|
reference_number: values.reference_number,
|
||||||
|
initial_balance_type:
|
||||||
|
(values.initial_balance_type_option?.value as string) || '',
|
||||||
|
nominal: Number(values.nominal.replace(/\D/g, '')) || 0,
|
||||||
|
note: values.note,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== Handler =====
|
||||||
|
const createInitialBalance = useCallback(
|
||||||
|
async (payload: CreateInitialBalance) => {
|
||||||
|
const response = await FinanceApi.createInitialBalances(payload);
|
||||||
|
|
||||||
|
if (isResponseError(response)) {
|
||||||
|
toast.error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Saldo awal berhasil ditambahkan');
|
||||||
|
router.refresh();
|
||||||
|
router.push('/finance');
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateInitialBalance = useCallback(
|
||||||
|
async (financeId: number, payload: UpdateInitialBalance) => {
|
||||||
|
const response = await FinanceApi.updateInitialBalances(
|
||||||
|
financeId,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isResponseError(response)) {
|
||||||
|
toast.error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Saldo awal berhasil diperbarui');
|
||||||
|
router.refresh();
|
||||||
|
router.push('/finance');
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className='w-full max-w-xl mx-auto'>
|
||||||
|
<div className='flex flex-col gap-6 p-6'>
|
||||||
|
<FormHeader
|
||||||
|
title={`${type === 'add' ? 'Tambah' : 'Ubah'} Saldo Awal`}
|
||||||
|
backUrl='/finance'
|
||||||
|
/>
|
||||||
|
<form className='flex flex-col gap-4' onSubmit={formik.handleSubmit}>
|
||||||
|
<SelectInput
|
||||||
|
label='Jenis Pihak'
|
||||||
|
placeholder='Pilih jenis pihak'
|
||||||
|
options={FINANCE_PARTY_TYPE_OPTIONS}
|
||||||
|
value={formik.values.party_type_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('party_type_option', value);
|
||||||
|
}}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.party_type_option &&
|
||||||
|
formik.errors.party_type_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.party_type_option &&
|
||||||
|
formik.errors.party_type_option
|
||||||
|
? formik.errors.party_type_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
label={
|
||||||
|
formik.values.party_type_option?.value
|
||||||
|
? formatTitleCase(
|
||||||
|
formik.values.party_type_option.value as string
|
||||||
|
)
|
||||||
|
: 'Pilih Jenis Pihak Dahulu'
|
||||||
|
}
|
||||||
|
placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis pihak dahulu'}`}
|
||||||
|
options={partyOptions}
|
||||||
|
value={formik.values.party_id_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('party_id_option', value);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingPartyOptions}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.party_id_option && formik.errors.party_id_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.party_id_option && formik.errors.party_id_option
|
||||||
|
? formik.errors.party_id_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
isDisabled={!formik.values.party_type_option?.value}
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
label='Bank'
|
||||||
|
placeholder='Pilih bank'
|
||||||
|
options={
|
||||||
|
isResponseSuccess(bankRawData)
|
||||||
|
? bankOptions.map((option) => ({
|
||||||
|
label:
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.alias +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.account_number +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.owner,
|
||||||
|
value: option.value,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
value={formik.values.bank_id_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('bank_id_option', value);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingBankOptions}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.bank_id_option && formik.errors.bank_id_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.bank_id_option && formik.errors.bank_id_option
|
||||||
|
? formik.errors.bank_id_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label='Nomor Referensi'
|
||||||
|
placeholder='Masukkan nomor referensi'
|
||||||
|
name='reference_number'
|
||||||
|
value={formik.values.reference_number}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.reference_number &&
|
||||||
|
formik.errors.reference_number
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.reference_number &&
|
||||||
|
formik.errors.reference_number
|
||||||
|
? formik.errors.reference_number
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
label='Tipe Saldo Awal'
|
||||||
|
placeholder='Pilih tipe saldo awal'
|
||||||
|
options={FINANCE_INITIAL_BALANCE_TYPE_OPTIONS}
|
||||||
|
value={formik.values.initial_balance_type_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('initial_balance_type_option', value);
|
||||||
|
}}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.initial_balance_type_option &&
|
||||||
|
formik.errors.initial_balance_type_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.initial_balance_type_option &&
|
||||||
|
formik.errors.initial_balance_type_option
|
||||||
|
? formik.errors.initial_balance_type_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label='Nominal'
|
||||||
|
placeholder='Masukkan nominal'
|
||||||
|
name='nominal'
|
||||||
|
value={formik.values.nominal}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(formik.touched.nominal && formik.errors.nominal)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.nominal && formik.errors.nominal
|
||||||
|
? formik.errors.nominal
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
allowNegative={false}
|
||||||
|
startAdornment={
|
||||||
|
formik.values.initial_balance_type_option?.value ===
|
||||||
|
'POSITIVE' ? (
|
||||||
|
<Icon icon='mdi:plus' />
|
||||||
|
) : formik.values.initial_balance_type_option?.value ===
|
||||||
|
'NEGATIVE' ? (
|
||||||
|
<Icon icon='mdi:minus' />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextArea
|
||||||
|
label='Catatan'
|
||||||
|
placeholder='Masukkan catatan'
|
||||||
|
name='note'
|
||||||
|
value={formik.values.note}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(formik.touched.note && formik.errors.note)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.note && formik.errors.note
|
||||||
|
? formik.errors.note
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div className='flex justify-center gap-4'>
|
||||||
|
<Button
|
||||||
|
type='reset'
|
||||||
|
color='warning'
|
||||||
|
className='w-min-24'
|
||||||
|
onClick={() => formik.resetForm()}
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
className='w-min-24'
|
||||||
|
disabled={formik.isSubmitting || !formik.isValid}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormFinanceAddInitialBalance;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
// Type for form values (includes option objects for SelectInput)
|
||||||
|
export type InjectionFormValues = {
|
||||||
|
bank_id_option: OptionType | null;
|
||||||
|
adjustment_date: string;
|
||||||
|
nominal: string;
|
||||||
|
note: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InjectionFormSchema = Yup.object<InjectionFormValues>({
|
||||||
|
bank_id_option: Yup.mixed()
|
||||||
|
.nullable()
|
||||||
|
.test(
|
||||||
|
'is-valid-option',
|
||||||
|
'Bank wajib diisi',
|
||||||
|
(value) => value !== null && value !== undefined
|
||||||
|
),
|
||||||
|
adjustment_date: Yup.string().required('Tanggal penyesuaian wajib diisi'),
|
||||||
|
nominal: Yup.string().required('Nominal wajib diisi'),
|
||||||
|
note: Yup.string().required('Catatan wajib diisi'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateInjectionFormSchema = InjectionFormSchema;
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
|
import TextArea from '@/components/input/TextArea';
|
||||||
|
import {
|
||||||
|
InjectionFormSchema,
|
||||||
|
InjectionFormValues,
|
||||||
|
} from '@/components/pages/finance/add/injection/FormFinanceInjection.schema';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
|
import { BankApi } from '@/services/api/master-data';
|
||||||
|
import {
|
||||||
|
CreateInjection,
|
||||||
|
Finance,
|
||||||
|
UpdateInjection,
|
||||||
|
} from '@/types/api/finance/finance';
|
||||||
|
import { Bank } from '@/types/api/master-data/bank';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
interface FormFinanceInjectionProps {
|
||||||
|
type?: 'add' | 'edit';
|
||||||
|
initialValues?: Finance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormFinanceInjection = ({
|
||||||
|
type = 'add',
|
||||||
|
initialValues,
|
||||||
|
}: FormFinanceInjectionProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// ===== Formik =====
|
||||||
|
const formikInitialValues = useMemo((): InjectionFormValues => {
|
||||||
|
return {
|
||||||
|
bank_id_option: initialValues?.bank
|
||||||
|
? {
|
||||||
|
label: initialValues.bank.name,
|
||||||
|
value: initialValues.bank.id,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
adjustment_date: initialValues?.payment_date || '',
|
||||||
|
nominal: initialValues?.nominal?.toString() || '',
|
||||||
|
note: initialValues?.notes || '',
|
||||||
|
};
|
||||||
|
}, [initialValues]);
|
||||||
|
|
||||||
|
const formik = useFormik<InjectionFormValues>({
|
||||||
|
initialValues: formikInitialValues,
|
||||||
|
validationSchema: InjectionFormSchema,
|
||||||
|
validateOnChange: true,
|
||||||
|
validateOnBlur: true,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
const payload = transformFormValuesToPayload(values);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'add':
|
||||||
|
await createInjection(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit':
|
||||||
|
if (initialValues?.id) {
|
||||||
|
await updateInjection(initialValues.id, payload);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== Options =====
|
||||||
|
const {
|
||||||
|
options: bankOptions,
|
||||||
|
rawData: bankRawData,
|
||||||
|
isLoadingOptions: isLoadingBankOptions,
|
||||||
|
} = useSelect<Bank>(BankApi.basePath, 'id', 'name', '', { limit: 'limit' });
|
||||||
|
|
||||||
|
// ===== Helper Functions =====
|
||||||
|
const transformFormValuesToPayload = (
|
||||||
|
values: InjectionFormValues
|
||||||
|
): CreateInjection => {
|
||||||
|
return {
|
||||||
|
bank_id: Number(values.bank_id_option?.value) || 0,
|
||||||
|
adjustment_date: formatDate(values.adjustment_date, 'YYYY-MM-DD'),
|
||||||
|
nominal: Number(values.nominal.replace(/\D/g, '')) || 0,
|
||||||
|
notes: values.note,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== Handler =====
|
||||||
|
const createInjection = useCallback(
|
||||||
|
async (payload: CreateInjection) => {
|
||||||
|
const response = await FinanceApi.createInjections(payload);
|
||||||
|
|
||||||
|
if (isResponseError(response)) {
|
||||||
|
toast.error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Injeksi dana berhasil ditambahkan');
|
||||||
|
router.refresh();
|
||||||
|
router.push('/finance');
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateInjection = useCallback(
|
||||||
|
async (financeId: number, payload: UpdateInjection) => {
|
||||||
|
const response = await FinanceApi.updateInjections(financeId, payload);
|
||||||
|
|
||||||
|
if (isResponseError(response)) {
|
||||||
|
toast.error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Injeksi dana berhasil diperbarui');
|
||||||
|
router.refresh();
|
||||||
|
router.push('/finance');
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className='w-full max-w-xl mx-auto'>
|
||||||
|
<div className='flex flex-col gap-6 p-6'>
|
||||||
|
<FormHeader
|
||||||
|
title={`${type === 'add' ? 'Tambah' : 'Ubah'} Injeksi Dana`}
|
||||||
|
backUrl='/finance'
|
||||||
|
/>
|
||||||
|
<form className='flex flex-col gap-4' onSubmit={formik.handleSubmit}>
|
||||||
|
<SelectInput
|
||||||
|
label='Bank'
|
||||||
|
placeholder='Pilih bank'
|
||||||
|
options={
|
||||||
|
isResponseSuccess(bankRawData)
|
||||||
|
? bankOptions.map((option) => ({
|
||||||
|
label:
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.alias +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.account_number +
|
||||||
|
' - ' +
|
||||||
|
bankRawData.data?.find(
|
||||||
|
(item) => item.id === option.value
|
||||||
|
)?.owner,
|
||||||
|
value: option.value,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
value={formik.values.bank_id_option}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('bank_id_option', value);
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingBankOptions}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.bank_id_option && formik.errors.bank_id_option
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.bank_id_option && formik.errors.bank_id_option
|
||||||
|
? formik.errors.bank_id_option
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
label='Tanggal Penyesuaian'
|
||||||
|
placeholder='Pilih tanggal penyesuaian'
|
||||||
|
name='adjustment_date'
|
||||||
|
value={formik.values.adjustment_date}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(
|
||||||
|
formik.touched.adjustment_date && formik.errors.adjustment_date
|
||||||
|
)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.adjustment_date && formik.errors.adjustment_date
|
||||||
|
? formik.errors.adjustment_date
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label='Nominal'
|
||||||
|
placeholder='Masukkan nominal'
|
||||||
|
name='nominal'
|
||||||
|
value={formik.values.nominal}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(formik.touched.nominal && formik.errors.nominal)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.nominal && formik.errors.nominal
|
||||||
|
? formik.errors.nominal
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
allowNegative={true}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextArea
|
||||||
|
label='Catatan'
|
||||||
|
placeholder='Masukkan catatan'
|
||||||
|
name='note'
|
||||||
|
value={formik.values.note}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={Boolean(formik.touched.note && formik.errors.note)}
|
||||||
|
errorMessage={
|
||||||
|
formik.touched.note && formik.errors.note
|
||||||
|
? formik.errors.note
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div className='flex justify-center gap-4'>
|
||||||
|
<Button
|
||||||
|
type='reset'
|
||||||
|
color='warning'
|
||||||
|
className='w-min-24'
|
||||||
|
onClick={() => formik.resetForm()}
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
className='w-min-24'
|
||||||
|
disabled={formik.isSubmitting || !formik.isValid}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormFinanceInjection;
|
||||||
@@ -4,6 +4,7 @@ import Badge from '@/components/Badge';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
@@ -78,18 +79,6 @@ const InventoryAdjustmentTable = () => {
|
|||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// id: 'before_quantity',
|
|
||||||
// header: 'Stok Sebelum',
|
|
||||||
// accessorFn: (row) =>
|
|
||||||
// formatNumber(String(row.product_warehouse?.quantity)),
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 'after_quantity',
|
|
||||||
// header: 'Stok Sesudah',
|
|
||||||
// accessorFn: (row) =>
|
|
||||||
// formatNumber(String(row.product_warehouse?.quantity)),
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
id: 'quantity',
|
id: 'quantity',
|
||||||
header: 'Kuantitas',
|
header: 'Kuantitas',
|
||||||
@@ -175,23 +164,17 @@ const InventoryAdjustmentTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.inventory.create'>
|
||||||
href='/inventory/adjustment/add'
|
<Button
|
||||||
variant='outline'
|
href='/inventory/adjustment/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
{/* <DebouncedTextInput
|
</RequirePermission>
|
||||||
name='search'
|
|
||||||
placeholder='Cari Stock Adjustment'
|
|
||||||
value={tableFilterState.search}
|
|
||||||
onChange={searchChangeHandler}
|
|
||||||
className={{ wrapper: 'sm:max-w-3xs' }}
|
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-row justify-end'>
|
<div className='flex flex-row justify-end'>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import SelectInput from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
@@ -28,15 +29,17 @@ const RowOptionsMenu = ({
|
|||||||
props: CellContext<Movement, unknown>;
|
props: CellContext<Movement, unknown>;
|
||||||
}) => (
|
}) => (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.inventory.transfer.detail'>
|
||||||
href={`/inventory/movement/detail/?movementId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/inventory/movement/detail/?movementId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
>
|
||||||
Detail
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -145,15 +148,17 @@ const MovementTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row gap-2'>
|
<div className='w-full flex flex-row gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.inventory.transfer.create'>
|
||||||
href='/inventory/movement/add'
|
<Button
|
||||||
variant='outline'
|
href='/inventory/movement/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Table from '@/components/Table';
|
|||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { cn, formatCurrency, formatNumber } from '@/lib/helper';
|
import { cn, formatCurrency, formatNumber } from '@/lib/helper';
|
||||||
@@ -31,15 +32,17 @@ const RowOptionsMenu = ({
|
|||||||
props: CellContext<InventoryProduct, unknown>;
|
props: CellContext<InventoryProduct, unknown>;
|
||||||
}) => (
|
}) => (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.inventory.product_stock.detail'>
|
||||||
href={`/inventory/product/detail?inventoryProductId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/inventory/product/detail?inventoryProductId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
>
|
||||||
Detail
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
import { useAuth } from '@/services/hooks/useAuth';
|
||||||
|
|
||||||
const RowsOptionsMenu = ({
|
const RowsOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
@@ -50,57 +52,71 @@ const RowsOptionsMenu = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.delivery_order.detail'>
|
||||||
href={`/marketing/detail?marketingId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='primary'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
{props.row.original.latest_approval.step_number != 1 && (
|
|
||||||
<Button
|
<Button
|
||||||
href={
|
href={`/marketing/detail?marketingId=${props.row.original.id}`}
|
||||||
props.row.original.latest_approval.step_number == 3
|
|
||||||
? `/marketing/detail/delivery-orders/edit?marketingId=${props.row.original.id}`
|
|
||||||
: props.row.original.latest_approval.step_number == 2
|
|
||||||
? `/marketing/add/delivery-orders?marketingId=${props.row.original.id}`
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
if (props.row.original.latest_approval.step_number == 2) {
|
|
||||||
deliveryClickHandler?.();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='success'
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:truck' width={16} height={16} />
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
Deliver
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
{props.row.original.latest_approval.step_number != 1 && (
|
||||||
|
<RequirePermission
|
||||||
|
permissions={
|
||||||
|
props.row.original.latest_approval.step_number == 3
|
||||||
|
? 'lti.marketing.delivery_order.update'
|
||||||
|
: 'lti.marketing.delivery_order.create'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
href={
|
||||||
|
props.row.original.latest_approval.step_number == 3
|
||||||
|
? `/marketing/detail/delivery-orders/edit?marketingId=${props.row.original.id}`
|
||||||
|
: props.row.original.latest_approval.step_number == 2
|
||||||
|
? `/marketing/add/delivery-orders?marketingId=${props.row.original.id}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (props.row.original.latest_approval.step_number == 2) {
|
||||||
|
deliveryClickHandler?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant='ghost'
|
||||||
|
color='success'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:truck' width={16} height={16} />
|
||||||
|
Deliver
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
{props.row.original.latest_approval.step_number != 3 && (
|
{props.row.original.latest_approval.step_number != 3 && (
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.sales_order.update'>
|
||||||
href={`/marketing/detail/sales-orders/edit?marketingId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/marketing/detail/sales-orders/edit?marketingId=${props.row.original.id}`}
|
||||||
color='warning'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='warning'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
>
|
||||||
Edit
|
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.sales_order.delete'>
|
||||||
onClick={deleteClickHandler}
|
<Button
|
||||||
variant='ghost'
|
onClick={deleteClickHandler}
|
||||||
color='error'
|
variant='ghost'
|
||||||
className='text-error hover:text-inherit justify-start text-sm'
|
color='error'
|
||||||
>
|
className='text-error hover:text-inherit justify-start text-sm'
|
||||||
<Icon icon='mdi:delete-outline' width={16} height={16} />
|
>
|
||||||
Delete
|
<Icon icon='mdi:delete-outline' width={16} height={16} />
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -116,6 +132,7 @@ const MarketingTable = () => {
|
|||||||
);
|
);
|
||||||
const [selectedItem, setSelectedItem] = useState<Marketing | null>(null);
|
const [selectedItem, setSelectedItem] = useState<Marketing | null>(null);
|
||||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||||
|
const { permissionCheck } = useAuth();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -270,10 +287,14 @@ const MarketingTable = () => {
|
|||||||
<div className='flex flex-col gap-4'>
|
<div className='flex flex-col gap-4'>
|
||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<TableToolbar
|
<TableToolbar
|
||||||
addButton={{
|
addButton={
|
||||||
href: '/marketing/add/sales-orders',
|
permissionCheck('lti.marketing.sales_order.create')
|
||||||
label: 'Tambah Sales Order',
|
? {
|
||||||
}}
|
href: '/marketing/add/sales-orders',
|
||||||
|
label: 'Tambah Sales Order',
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
search={{
|
search={{
|
||||||
value: search,
|
value: search,
|
||||||
onChange: searchChangeHandler,
|
onChange: searchChangeHandler,
|
||||||
@@ -281,30 +302,35 @@ const MarketingTable = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
color='success'
|
<Button
|
||||||
onClick={approveClickHandler}
|
color='success'
|
||||||
className='justify-start text-sm'
|
onClick={approveClickHandler}
|
||||||
disabled={disableApprove}
|
className='justify-start text-sm'
|
||||||
>
|
disabled={disableApprove}
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
>
|
||||||
Approve
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
</Button>
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
color='error'
|
<Button
|
||||||
onClick={rejectClickHandler}
|
color='error'
|
||||||
className='justify-start text-sm'
|
onClick={rejectClickHandler}
|
||||||
disabled={disableReject}
|
className='justify-start text-sm'
|
||||||
>
|
disabled={disableReject}
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
>
|
||||||
Reject
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
</Button>
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
<TableRowSizeSelector
|
<TableRowSizeSelector
|
||||||
value={pageSize}
|
value={pageSize}
|
||||||
onChange={pageSizeChangeHandler}
|
onChange={pageSizeChangeHandler}
|
||||||
options={ROWS_OPTIONS}
|
options={ROWS_OPTIONS}
|
||||||
|
className='flex sm:flex-row flex-col gap-3 items-end justify-end'
|
||||||
>
|
>
|
||||||
{/* select multiple product */}
|
{/* select multiple product */}
|
||||||
<SelectInput
|
<SelectInput
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { useState } from 'react';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import SalesOrderExport from '@/components/pages/marketing/pdf/SalesOrderExport';
|
import SalesOrderExport from '@/components/pages/marketing/pdf/SalesOrderExport';
|
||||||
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
const MarketingDetail = ({
|
const MarketingDetail = ({
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -134,45 +135,58 @@ const MarketingDetail = ({
|
|||||||
<div className='flex-row flex gap-3'>
|
<div className='flex-row flex gap-3'>
|
||||||
{initialValues?.latest_approval?.step_number == 1 && (
|
{initialValues?.latest_approval?.step_number == 1 && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
color='success'
|
<Button
|
||||||
onClick={approveClickHandler}
|
color='success'
|
||||||
disabled={
|
onClick={approveClickHandler}
|
||||||
initialValues?.latest_approval?.step_number == 1 &&
|
disabled={
|
||||||
initialValues?.latest_approval?.action == 'REJECTED'
|
initialValues?.latest_approval?.step_number == 1 &&
|
||||||
}
|
initialValues?.latest_approval?.action == 'REJECTED'
|
||||||
>
|
}
|
||||||
<Icon icon='mdi:check' width={24} height={24} />
|
>
|
||||||
Approve
|
<Icon icon='mdi:check' width={24} height={24} />
|
||||||
</Button>
|
Approve
|
||||||
<Button
|
</Button>
|
||||||
color='error'
|
</RequirePermission>
|
||||||
onClick={rejectClickHandler}
|
|
||||||
disabled={
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
initialValues?.latest_approval?.step_number == 1 &&
|
<Button
|
||||||
initialValues?.latest_approval?.action == 'REJECTED'
|
color='error'
|
||||||
}
|
onClick={rejectClickHandler}
|
||||||
>
|
disabled={
|
||||||
<Icon icon='mdi:close' width={24} height={24} />
|
initialValues?.latest_approval?.step_number == 1 &&
|
||||||
Reject
|
initialValues?.latest_approval?.action == 'REJECTED'
|
||||||
</Button>
|
}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:close' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{initialValues?.latest_approval?.step_number != 1 && (
|
{initialValues?.latest_approval?.step_number != 1 && (
|
||||||
<Button
|
<RequirePermission
|
||||||
color='success'
|
permissions={
|
||||||
href={
|
|
||||||
initialValues?.latest_approval?.step_number == 3
|
initialValues?.latest_approval?.step_number == 3
|
||||||
? `/marketing/detail/delivery-orders/edit?marketingId=${initialValues?.id}`
|
? 'lti.marketing.delivery_order.update'
|
||||||
: `/marketing/add/delivery-orders?marketingId=${initialValues?.id}`
|
: 'lti.marketing.delivery_order.create'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:truck' width={24} height={24} />
|
<Button
|
||||||
{initialValues?.latest_approval?.step_number == 3
|
color='success'
|
||||||
? 'Edit '
|
href={
|
||||||
: 'Tambah '}
|
initialValues?.latest_approval?.step_number == 3
|
||||||
Delivery Order
|
? `/marketing/detail/delivery-orders/edit?marketingId=${initialValues?.id}`
|
||||||
</Button>
|
: `/marketing/add/delivery-orders?marketingId=${initialValues?.id}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:truck' width={24} height={24} />
|
||||||
|
{initialValues?.latest_approval?.step_number == 3
|
||||||
|
? 'Edit '
|
||||||
|
: 'Tambah '}
|
||||||
|
Delivery Order
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -413,19 +427,23 @@ const MarketingDetail = ({
|
|||||||
)}
|
)}
|
||||||
<div className='flex flex-row gap-3'>
|
<div className='flex flex-row gap-3'>
|
||||||
{initialValues?.latest_approval?.step_number != 3 && (
|
{initialValues?.latest_approval?.step_number != 3 && (
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.sales_order.update'>
|
||||||
color='warning'
|
<Button
|
||||||
type='button'
|
color='warning'
|
||||||
href={`/marketing/detail/${initialValues?.latest_approval.step_number == 3 ? 'delivery-orders' : 'sales-orders'}/edit?marketingId=${initialValues?.id}`}
|
type='button'
|
||||||
>
|
href={`/marketing/detail/${initialValues?.latest_approval.step_number == 3 ? 'delivery-orders' : 'sales-orders'}/edit?marketingId=${initialValues?.id}`}
|
||||||
<Icon icon='mdi:pencil' width={24} height={24} />
|
>
|
||||||
Edit
|
<Icon icon='mdi:pencil' width={24} height={24} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
<Button color='error' onClick={deleteClickHandler}>
|
<RequirePermission permissions='lti.marketing.sales_order.delete'>
|
||||||
<Icon icon='mdi:delete' width={24} height={24} />
|
<Button color='error' onClick={deleteClickHandler}>
|
||||||
Hapus
|
<Icon icon='mdi:delete' width={24} height={24} />
|
||||||
</Button>
|
Hapus
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import DeliveryOrderProductTable from '@/components/pages/marketing/form/table-v
|
|||||||
import DeliveryOrderProductForm from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct';
|
import DeliveryOrderProductForm from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct';
|
||||||
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||||
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable);
|
const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable);
|
||||||
const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm);
|
const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm);
|
||||||
@@ -575,7 +576,7 @@ const MarketingForm = ({
|
|||||||
wrapper: 'bg-white w-full',
|
wrapper: 'bg-white w-full',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='grid grid-cols-2 gap-3 mt-3'>
|
<div className='grid sm:grid-cols-2 gap-3 mt-3'>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
label='Pelanggan'
|
label='Pelanggan'
|
||||||
options={customerOptions}
|
options={customerOptions}
|
||||||
@@ -650,7 +651,7 @@ const MarketingForm = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Input Notes */}
|
{/* Input Notes */}
|
||||||
<div className='grid grid-cols-2 gap-3'>
|
<div className='grid sm:grid-cols-2 gap-3'>
|
||||||
<DebouncedTextArea
|
<DebouncedTextArea
|
||||||
required
|
required
|
||||||
name='notes'
|
name='notes'
|
||||||
@@ -689,15 +690,17 @@ const MarketingForm = ({
|
|||||||
{/* Actions button */}
|
{/* Actions button */}
|
||||||
{formType == 'edit' && (
|
{formType == 'edit' && (
|
||||||
<div className='flex flex-row justify-start'>
|
<div className='flex flex-row justify-start'>
|
||||||
<Button
|
<RequirePermission permissions='lti.marketing.sales_order.delete'>
|
||||||
type='button'
|
<Button
|
||||||
color='error'
|
type='button'
|
||||||
onClick={handleDelete}
|
color='error'
|
||||||
isLoading={isLoading}
|
onClick={handleDelete}
|
||||||
>
|
isLoading={isLoading}
|
||||||
<Icon icon='mdi:trash' width={24} height={24} />
|
>
|
||||||
Hapus
|
<Icon icon='mdi:trash' width={24} height={24} />
|
||||||
</Button>
|
Hapus
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='grid grid-cols-2 gap-4'>
|
<div className='grid sm:grid-cols-2 gap-4'>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
options={options}
|
options={options}
|
||||||
label='Produk'
|
label='Produk'
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const SalesOrderProductForm = ({
|
|||||||
{/* <small className='block text-rose-500'>
|
{/* <small className='block text-rose-500'>
|
||||||
{JSON.stringify(formik.errors)}
|
{JSON.stringify(formik.errors)}
|
||||||
</small> */}
|
</small> */}
|
||||||
<div className='grid grid-cols-2 gap-4 z-200'>
|
<div className='grid sm:grid-cols-2 gap-4 z-200'>
|
||||||
<PatternInput
|
<PatternInput
|
||||||
name='vehicle_number'
|
name='vehicle_number'
|
||||||
label='No. Polisi'
|
label='No. Polisi'
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Area } from '@/types/api/master-data/area';
|
import { Area } from '@/types/api/master-data/area';
|
||||||
import { AreaApi } from '@/services/api/master-data';
|
import { AreaApi } from '@/services/api/master-data';
|
||||||
@@ -34,40 +35,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.area.detail'>
|
||||||
href={`/master-data/area/detail/?areaId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/area/detail/?areaId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/area/detail/edit/?areaId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='text-error hover:text-inherit'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.area.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/area/detail/edit/?areaId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.area.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='text-error hover:text-inherit'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -192,15 +199,19 @@ const AreasTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<div className='w-full flex flex-row'>
|
||||||
href='/master-data/area/add'
|
<RequirePermission permissions='lti.master.area.create'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='primary'
|
href='/master-data/area/add'
|
||||||
className='w-full sm:w-fit'
|
variant='outline'
|
||||||
>
|
color='primary'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
className='w-full sm:w-fit'
|
||||||
Tambah
|
>
|
||||||
</Button>
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Button from '@/components/Button';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AreaFormSchema,
|
AreaFormSchema,
|
||||||
@@ -160,36 +161,40 @@ const AreaForm = ({ type = 'add', initialValues }: AreaFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.area.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteAreaClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/area/detail/edit/?areaId=${initialValues?.id}`}
|
onClick={deleteAreaClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.area.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/area/detail/edit/?areaId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Bank } from '@/types/api/master-data/bank';
|
import { Bank } from '@/types/api/master-data/bank';
|
||||||
import { BankApi } from '@/services/api/master-data';
|
import { BankApi } from '@/services/api/master-data';
|
||||||
@@ -34,40 +35,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.banks.detail'>
|
||||||
href={`/master-data/bank/detail/?bankId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/bank/detail/?bankId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/bank/detail/edit/?bankId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.banks.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/bank/detail/edit/?bankId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.banks.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -205,15 +212,17 @@ const BanksTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.banks.create'>
|
||||||
href='/master-data/bank/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/bank/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Button from '@/components/Button';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BankFormSchema,
|
BankFormSchema,
|
||||||
@@ -208,36 +209,40 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.banks.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteBankClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/bank/detail/edit/?bankId=${initialValues?.id}`}
|
onClick={deleteBankClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.banks.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/bank/detail/edit/?bankId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Table from '@/components/Table';
|
|||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
@@ -32,38 +33,44 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.customer.detail'>
|
||||||
href={`/master-data/customer/detail/?customerId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/customer/detail/?customerId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
href={`/master-data/customer/detail/edit/?customerId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
<RequirePermission permissions='lti.master.customer.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/customer/detail/edit/?customerId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
<RequirePermission permissions='lti.master.customer.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -200,15 +207,17 @@ const CustomersTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.customer.create'>
|
||||||
href='/master-data/customer/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/customer/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { UserApi } from '@/services/api/user';
|
import { UserApi } from '@/services/api/user';
|
||||||
import { TYPE_OPTIONS } from '@/config/constant';
|
import { TYPE_OPTIONS } from '@/config/constant';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
interface CustomerFormProps {
|
interface CustomerFormProps {
|
||||||
formType?: 'add' | 'edit' | 'detail';
|
formType?: 'add' | 'edit' | 'detail';
|
||||||
@@ -319,36 +320,40 @@ const CustomerForm = ({
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{formType !== 'add' && (
|
{formType !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.customer.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteCustomerHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{formType !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/customer/detail/edit/?customerId=${initialValues?.id}`}
|
onClick={deleteCustomerHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{formType !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.customer.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/customer/detail/edit/?customerId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Fcr } from '@/types/api/master-data/fcr';
|
import { Fcr } from '@/types/api/master-data/fcr';
|
||||||
import { FcrApi } from '@/services/api/master-data';
|
import { FcrApi } from '@/services/api/master-data';
|
||||||
@@ -34,40 +35,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.fcr.detail'>
|
||||||
href={`/master-data/fcr/detail/?fcrId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/fcr/detail/?fcrId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/fcr/detail/edit/?fcrId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.fcr.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/fcr/detail/edit/?fcrId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.fcr.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -192,15 +199,17 @@ const FcrsTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.fcr.create'>
|
||||||
href='/master-data/fcr/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/fcr/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Button from '@/components/Button';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FcrFormSchema,
|
FcrFormSchema,
|
||||||
@@ -296,36 +297,40 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.fcr.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteFcrClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/fcr/detail/edit/?fcrId=${initialValues?.id}`}
|
onClick={deleteFcrClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.fcr.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/fcr/detail/edit/?fcrId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useModal } from '@/components/Modal';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
@@ -32,48 +33,54 @@ const RowsOptions = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.flocks.update'>
|
||||||
href={`/master-data/flock/detail/edit/?flockId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/flock/detail/edit/?flockId=${props.row.original.id}`}
|
||||||
color='warning'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='warning'
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:edit-outline'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Edit
|
<Icon
|
||||||
</Button>
|
icon='material-symbols:edit-outline'
|
||||||
<Button
|
width={16}
|
||||||
href={`/master-data/flock/detail/?flockId=${props.row.original.id}`}
|
height={16}
|
||||||
variant='ghost'
|
className='justify-start text-sm'
|
||||||
color='primary'
|
/>
|
||||||
className='justify-start text-sm'
|
Edit
|
||||||
>
|
</Button>
|
||||||
<Icon
|
</RequirePermission>
|
||||||
icon='mdi:eye-outline'
|
<RequirePermission permissions='lti.master.flocks.detail'>
|
||||||
width={16}
|
<Button
|
||||||
height={16}
|
href={`/master-data/flock/detail/?flockId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Detail
|
<Icon
|
||||||
</Button>
|
icon='mdi:eye-outline'
|
||||||
<Button
|
width={16}
|
||||||
onClick={deleteClickHandler}
|
height={16}
|
||||||
variant='ghost'
|
className='justify-start text-sm'
|
||||||
color='error'
|
/>
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
Detail
|
||||||
>
|
</Button>
|
||||||
<Icon
|
</RequirePermission>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<RequirePermission permissions='lti.master.flocks.delete'>
|
||||||
width={16}
|
<Button
|
||||||
height={16}
|
onClick={deleteClickHandler}
|
||||||
className='justify-start text-sm'
|
variant='ghost'
|
||||||
/>
|
color='error'
|
||||||
Delete
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
</Button>
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -196,15 +203,17 @@ const FlockTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.flocks.create'>
|
||||||
href='/master-data/flock/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/flock/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Icon } from '@iconify/react';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
interface FlockCustomProps {
|
interface FlockCustomProps {
|
||||||
formType?: 'add' | 'edit' | 'detail';
|
formType?: 'add' | 'edit' | 'detail';
|
||||||
@@ -130,35 +131,39 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{formType !== 'add' && (
|
{formType !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.flocks.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={() => deleteModal.openModal()}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
{formType !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/flock/detail/edit/?flockId=${initialValues?.id}`}
|
onClick={() => deleteModal.openModal()}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
{formType !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.flocks.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/flock/detail/edit/?flockId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import { KandangApi } from '@/services/api/master-data';
|
import { KandangApi } from '@/services/api/master-data';
|
||||||
@@ -39,40 +40,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.kandangs.detail'>
|
||||||
href={`/master-data/kandang/detail/?kandangId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/kandang/detail/?kandangId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/kandang/detail/edit/?kandangId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.kandangs.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/kandang/detail/edit/?kandangId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.kandangs.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -243,15 +250,19 @@ const KandangsTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<div className='w-full flex flex-row'>
|
||||||
href='/master-data/kandang/add'
|
<RequirePermission permissions='lti.master.kandangs.create'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='primary'
|
href='/master-data/kandang/add'
|
||||||
className='w-full sm:w-fit'
|
variant='outline'
|
||||||
>
|
color='primary'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
className='w-full sm:w-fit'
|
||||||
Tambah
|
>
|
||||||
</Button>
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import TextInput from '@/components/input/TextInput';
|
|||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
KandangFormSchema,
|
KandangFormSchema,
|
||||||
@@ -285,36 +286,40 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.kandangs.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteKandangClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/kandang/detail/edit/?kandangId=${initialValues?.id}`}
|
onClick={deleteKandangClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.kandangs.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/kandang/detail/edit/?kandangId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Location } from '@/types/api/master-data/location';
|
import { Location } from '@/types/api/master-data/location';
|
||||||
import { LocationApi } from '@/services/api/master-data';
|
import { LocationApi } from '@/services/api/master-data';
|
||||||
@@ -39,40 +40,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.locations.detail'>
|
||||||
href={`/master-data/location/detail/?locationId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/location/detail/?locationId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/location/detail/edit/?locationId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.locations.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/location/detail/edit/?locationId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.locations.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -230,15 +237,19 @@ const LocationsTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<div className='w-full flex flex-row'>
|
||||||
href='/master-data/location/add'
|
<RequirePermission permissions='lti.master.locations.create'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='primary'
|
href='/master-data/location/add'
|
||||||
className='w-full sm:w-fit'
|
variant='outline'
|
||||||
>
|
color='primary'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
className='w-full sm:w-fit'
|
||||||
Tambah
|
>
|
||||||
</Button>
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import TextInput from '@/components/input/TextInput';
|
|||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LocationFormSchema,
|
LocationFormSchema,
|
||||||
@@ -229,36 +230,40 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.locations.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteLocationClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/location/detail/edit/?locationId=${initialValues?.id}`}
|
onClick={deleteLocationClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.locations.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/location/detail/edit/?locationId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Nonstock } from '@/types/api/master-data/nonstock';
|
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||||
import { NonstockApi } from '@/services/api/master-data';
|
import { NonstockApi } from '@/services/api/master-data';
|
||||||
@@ -39,40 +40,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.nonstocks.detail'>
|
||||||
href={`/master-data/nonstock/detail/?nonstockId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/nonstock/detail/?nonstockId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/nonstock/detail/edit/?nonstockId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.nonstocks.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/nonstock/detail/edit/?nonstockId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.nonstocks.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -242,15 +249,17 @@ const NonstocksTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.nonstocks.create'>
|
||||||
href='/master-data/nonstock/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/nonstock/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import TextInput from '@/components/input/TextInput';
|
|||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NonstockFormSchema,
|
NonstockFormSchema,
|
||||||
@@ -298,36 +299,40 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.nonstocks.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteNonstockClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/nonstock/detail/edit/?nonstockId=${initialValues?.id}`}
|
onClick={deleteNonstockClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.nonstocks.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/nonstock/detail/edit/?nonstockId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { ProductCategory } from '@/types/api/master-data/product-category';
|
import { ProductCategory } from '@/types/api/master-data/product-category';
|
||||||
import { ProductCategoryApi } from '@/services/api/master-data';
|
import { ProductCategoryApi } from '@/services/api/master-data';
|
||||||
@@ -34,38 +35,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.product_categories.detail'>
|
||||||
href={`/master-data/product-category/detail/?productCategoryId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/product-category/detail/?productCategoryId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
href={`/master-data/product-category/detail/edit/?productCategoryId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:delete-outline'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.product_categories.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/product-category/detail/edit/?productCategoryId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.product_categories.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='mdi:delete-outline'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -193,15 +202,17 @@ const ProductCategoryTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.product_categories.create'>
|
||||||
href='/master-data/product-category/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/product-category/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
name='search'
|
name='search'
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Button from '@/components/Button';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ProductCategoryFormSchema,
|
ProductCategoryFormSchema,
|
||||||
@@ -183,36 +184,40 @@ const ProductCategoryForm = ({
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.product_categories.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteProductCategoryClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/product-category/detail/edit/?productCategoryId=${initialValues?.id}`}
|
onClick={deleteProductCategoryClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.product_categories.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/product-category/detail/edit/?productCategoryId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Product } from '@/types/api/master-data/product';
|
import { Product } from '@/types/api/master-data/product';
|
||||||
import { ProductApi } from '@/services/api/master-data';
|
import { ProductApi } from '@/services/api/master-data';
|
||||||
@@ -38,38 +39,44 @@ const RowOptionsMenu = ({
|
|||||||
deleteClickHandler: () => void;
|
deleteClickHandler: () => void;
|
||||||
}) => (
|
}) => (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.products.detail'>
|
||||||
href={`/master-data/product/detail/?productId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/product/detail/?productId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
href={`/master-data/product/detail/edit/?productId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
<RequirePermission permissions='lti.master.products.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/product/detail/edit/?productId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
<RequirePermission permissions='lti.master.products.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -273,15 +280,17 @@ const ProductsTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.products.create'>
|
||||||
href='/master-data/product/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/product/add'
|
||||||
className='w-full sm:w-fit'
|
variant='outline'
|
||||||
color='primary'
|
className='w-full sm:w-fit'
|
||||||
>
|
color='primary'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
name='search'
|
name='search'
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import SelectInput, {
|
|||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ProductFormSchema,
|
ProductFormSchema,
|
||||||
@@ -413,35 +414,39 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.products.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteProductClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/product/detail/edit/?productId=${initialValues?.id}`}
|
onClick={deleteProductClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.products.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/product/detail/edit/?productId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||||
|
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
|
||||||
|
import { ProductionStandard } from '@/types/api/master-data/production-standard';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { ProductionStandardApi } from '@/services/api/master-data';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import { CellContext } from '@tanstack/react-table';
|
||||||
|
import { useModal } from '@/components/Modal';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { cn } from '@/lib/helper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
|
const RowOptionsMenu = ({
|
||||||
|
type = 'dropdown',
|
||||||
|
props,
|
||||||
|
deleteClickHandler,
|
||||||
|
}: {
|
||||||
|
type: 'dropdown' | 'collapse';
|
||||||
|
props: CellContext<ProductionStandard, unknown>;
|
||||||
|
deleteClickHandler: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<RowOptionsMenuWrapper type={type}>
|
||||||
|
<RequirePermission permissions='lti.master.production_standards.detail'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/production-standard/detail/?productionStandardId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='primary'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.production_standards.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/production-standard/detail/edit/?productionStandardId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.production_standards.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='text-error hover:text-inherit'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</RowOptionsMenuWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProductionStandardTable = () => {
|
||||||
|
const deleteModal = useModal();
|
||||||
|
|
||||||
|
const [selectedProductionStandard, setSelectedProductionStandard] = useState<
|
||||||
|
ProductionStandard | undefined
|
||||||
|
>(undefined);
|
||||||
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: productionStandards,
|
||||||
|
isLoading: productionStandardsLoading,
|
||||||
|
mutate: refreshProductionStandards,
|
||||||
|
} = useSWR(
|
||||||
|
`${ProductionStandardApi.basePath}`,
|
||||||
|
ProductionStandardApi.getAllFetcher
|
||||||
|
);
|
||||||
|
|
||||||
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
|
await ProductionStandardApi.delete(
|
||||||
|
selectedProductionStandard?.id as number
|
||||||
|
);
|
||||||
|
refreshProductionStandards();
|
||||||
|
|
||||||
|
deleteModal.closeModal();
|
||||||
|
toast.success('Successfully delete Production Standard!');
|
||||||
|
setIsDeleteLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='flex flex-col gap-6 p-6'>
|
||||||
|
<div className='flex flex-row gap-6 justify-start'>
|
||||||
|
<RequirePermission permissions='lti.master.production_standards.create'>
|
||||||
|
<Button
|
||||||
|
href='/master-data/production-standard/add'
|
||||||
|
variant='outline'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:plus' /> Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</div>
|
||||||
|
<RequirePermission permissions='lti.master.production_standards.list'>
|
||||||
|
<Table<ProductionStandard>
|
||||||
|
data={
|
||||||
|
isResponseSuccess(productionStandards)
|
||||||
|
? productionStandards.data
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: 'No',
|
||||||
|
accessorFn: (row, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Nama',
|
||||||
|
accessorKey: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Kategori',
|
||||||
|
accessorFn: (row) => row.project_category,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Aksi',
|
||||||
|
cell: (props) => {
|
||||||
|
const currentPageSize =
|
||||||
|
props.table.getPaginationRowModel().rows.length;
|
||||||
|
const currentPageRows =
|
||||||
|
props.table.getPaginationRowModel().flatRows;
|
||||||
|
const currentRowRelativeIndex =
|
||||||
|
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||||
|
|
||||||
|
const isLast2Rows =
|
||||||
|
currentRowRelativeIndex > currentPageSize - 2;
|
||||||
|
|
||||||
|
const deleteClickHandler = () => {
|
||||||
|
setSelectedProductionStandard(props.row.original);
|
||||||
|
deleteModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{currentPageSize > 2 && (
|
||||||
|
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||||
|
<RowOptionsMenu
|
||||||
|
type='dropdown'
|
||||||
|
props={props}
|
||||||
|
deleteClickHandler={deleteClickHandler}
|
||||||
|
/>
|
||||||
|
</RowDropdownOptions>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentPageSize <= 2 && (
|
||||||
|
<RowCollapseOptions>
|
||||||
|
<RowOptionsMenu
|
||||||
|
type='collapse'
|
||||||
|
props={props}
|
||||||
|
deleteClickHandler={deleteClickHandler}
|
||||||
|
/>
|
||||||
|
</RowCollapseOptions>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
className={{
|
||||||
|
headerColumnClassName: cn(
|
||||||
|
TABLE_DEFAULT_STYLING.headerColumnClassName,
|
||||||
|
'last:flex last:flex-row last:justify-end'
|
||||||
|
),
|
||||||
|
bodyColumnClassName: cn(
|
||||||
|
TABLE_DEFAULT_STYLING.bodyColumnClassName,
|
||||||
|
'last:flex last:flex-row last:justify-end'
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RequirePermission>
|
||||||
|
</div>
|
||||||
|
<RequirePermission permissions='lti.master.production_standards.delete'>
|
||||||
|
<ConfirmationModal
|
||||||
|
ref={deleteModal.ref}
|
||||||
|
type='error'
|
||||||
|
text={`Apakah anda yakin ingin menghapus data Production Standard ini (${selectedProductionStandard?.name})?`}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Tidak',
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: 'error',
|
||||||
|
isLoading: isDeleteLoading,
|
||||||
|
onClick: confirmationModalDeleteClickHandler,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RequirePermission>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductionStandardTable;
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
// Schema for LAYING category (production_standard_details is required)
|
||||||
|
const LayingRepeaterFormSchema = Yup.object({
|
||||||
|
week: Yup.number().required('Minggu wajib diisi!'),
|
||||||
|
production_standard_uniformity_details: Yup.object({
|
||||||
|
target_mean_bw: Yup.number().required('Berat rata-rata wajib diisi!'),
|
||||||
|
max_depletion: Yup.number().required('Maksimal depletion wajib diisi!'),
|
||||||
|
min_uniformity: Yup.number().required('Minimal uniformitas wajib diisi!'),
|
||||||
|
feed_intake: Yup.number().required('Pengambilan makanan wajib diisi!'),
|
||||||
|
}),
|
||||||
|
production_standard_details: Yup.object({
|
||||||
|
target_hen_day_production: Yup.number().required(
|
||||||
|
'Produksi telur per hari wajib diisi!'
|
||||||
|
),
|
||||||
|
target_hen_house_production: Yup.number().required(
|
||||||
|
'Produksi telur per kandang wajib diisi!'
|
||||||
|
),
|
||||||
|
target_egg_weight: Yup.number().required('Berat telur wajib diisi!'),
|
||||||
|
target_egg_mass: Yup.number().required('Massa telur wajib diisi!'),
|
||||||
|
}).required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Schema for GROWING category (production_standard_details is optional)
|
||||||
|
const GrowingRepeaterFormSchema = Yup.object({
|
||||||
|
week: Yup.number().required('Minggu wajib diisi!'),
|
||||||
|
production_standard_uniformity_details: Yup.object({
|
||||||
|
target_mean_bw: Yup.number().required('Berat rata-rata wajib diisi!'),
|
||||||
|
max_depletion: Yup.number().required('Maksimal depletion wajib diisi!'),
|
||||||
|
min_uniformity: Yup.number().required('Minimal uniformitas wajib diisi!'),
|
||||||
|
feed_intake: Yup.number().required('Pengambilan makanan wajib diisi!'),
|
||||||
|
}),
|
||||||
|
production_standard_details: Yup.object({
|
||||||
|
target_hen_day_production: Yup.number().optional(),
|
||||||
|
target_hen_house_production: Yup.number().optional(),
|
||||||
|
target_egg_weight: Yup.number().optional(),
|
||||||
|
target_egg_mass: Yup.number().optional(),
|
||||||
|
}).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Explicit types for better type inference
|
||||||
|
export type LayingRepeaterFormValues = Yup.InferType<
|
||||||
|
typeof LayingRepeaterFormSchema
|
||||||
|
>;
|
||||||
|
export type GrowingRepeaterFormValues = Yup.InferType<
|
||||||
|
typeof GrowingRepeaterFormSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Union type for repeater form values
|
||||||
|
export type ProductionStandardRepeaterFormSchemaValues =
|
||||||
|
| LayingRepeaterFormValues
|
||||||
|
| GrowingRepeaterFormValues;
|
||||||
|
|
||||||
|
// Dynamic schema factory for repeater form based on project category
|
||||||
|
export const createProductionStandardRepeaterFormSchema = (
|
||||||
|
category: string
|
||||||
|
) => {
|
||||||
|
// For LAYING category, production_standard_details is required
|
||||||
|
if (category === 'LAYING') {
|
||||||
|
return LayingRepeaterFormSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For GROWING category, production_standard_details is optional
|
||||||
|
return GrowingRepeaterFormSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamic schema factory for main form based on project category
|
||||||
|
export const createProductionStandardFormSchema = (category: string) => {
|
||||||
|
return Yup.object({
|
||||||
|
name: Yup.string().required('Nama wajib diisi!'),
|
||||||
|
project_category: Yup.string().required('Kategori proyek wajib diisi!'),
|
||||||
|
details: Yup.array().of(
|
||||||
|
createProductionStandardRepeaterFormSchema(category)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static schemas for backward compatibility (default to LAYING)
|
||||||
|
export const ProductionStandardFormSchema =
|
||||||
|
createProductionStandardFormSchema('LAYING');
|
||||||
|
|
||||||
|
export const UpdateProductionStandardFormSchema = ProductionStandardFormSchema;
|
||||||
|
|
||||||
|
export type ProductionStandardFormValues = Yup.InferType<
|
||||||
|
typeof ProductionStandardFormSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const ProductionStandardRepeaterFormSchema = LayingRepeaterFormSchema;
|
||||||
|
|
||||||
|
export const UpdateProductionStandardRepeaterFormSchema =
|
||||||
|
ProductionStandardRepeaterFormSchema;
|
||||||
+1247
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ import Table from '@/components/Table';
|
|||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
@@ -32,48 +33,54 @@ const RowOptions = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.suppliers.detail'>
|
||||||
href={`/master-data/supplier/detail/?supplierId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/supplier/detail/?supplierId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:eye-outline'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Detail
|
<Icon
|
||||||
</Button>
|
icon='mdi:eye-outline'
|
||||||
<Button
|
width={16}
|
||||||
href={`/master-data/supplier/detail/edit/?supplierId=${props.row.original.id}`}
|
height={16}
|
||||||
variant='ghost'
|
className='justify-start text-sm'
|
||||||
color='warning'
|
/>
|
||||||
className='justify-start text-sm'
|
Detail
|
||||||
>
|
</Button>
|
||||||
<Icon
|
</RequirePermission>
|
||||||
icon='material-symbols:edit-outline'
|
<RequirePermission permissions='lti.master.suppliers.update'>
|
||||||
width={16}
|
<Button
|
||||||
height={16}
|
href={`/master-data/supplier/detail/edit/?supplierId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Edit
|
<Icon
|
||||||
</Button>
|
icon='material-symbols:edit-outline'
|
||||||
<Button
|
width={16}
|
||||||
onClick={deleteClickHandler}
|
height={16}
|
||||||
variant='ghost'
|
className='justify-start text-sm'
|
||||||
color='error'
|
/>
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
Edit
|
||||||
>
|
</Button>
|
||||||
<Icon
|
</RequirePermission>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<RequirePermission permissions='lti.master.suppliers.delete'>
|
||||||
width={16}
|
<Button
|
||||||
height={16}
|
onClick={deleteClickHandler}
|
||||||
className='justify-start text-sm'
|
variant='ghost'
|
||||||
/>
|
color='error'
|
||||||
Delete
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
</Button>
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -219,15 +226,17 @@ const SuppliersTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.suppliers.create'>
|
||||||
href='/master-data/supplier/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/supplier/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import TextInput from '@/components/input/TextInput';
|
|||||||
import TextArea from '@/components/input/TextArea';
|
import TextArea from '@/components/input/TextArea';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
interface SupplierCustomProps {
|
interface SupplierCustomProps {
|
||||||
formType?: 'add' | 'edit' | 'detail';
|
formType?: 'add' | 'edit' | 'detail';
|
||||||
@@ -406,36 +407,40 @@ const SupplierForm = ({
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{formType !== 'add' && (
|
{formType !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.suppliers.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteSupplierHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{formType !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/supplier/detail/edit/?supplierId=${initialValues?.id}`}
|
onClick={deleteSupplierHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{formType !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.suppliers.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/supplier/detail/edit/?supplierId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Uom } from '@/types/api/master-data/uom';
|
import { Uom } from '@/types/api/master-data/uom';
|
||||||
import { UomApi } from '@/services/api/master-data';
|
import { UomApi } from '@/services/api/master-data';
|
||||||
@@ -34,40 +35,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.uoms.detail'>
|
||||||
href={`/master-data/uom/detail/?uomId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/uom/detail/?uomId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/uom/detail/edit/?uomId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.uoms.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/uom/detail/edit/?uomId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.uoms.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -192,15 +199,17 @@ const UomsTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.uoms.create'>
|
||||||
href='/master-data/uom/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/uom/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Button from '@/components/Button';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UomFormSchema,
|
UomFormSchema,
|
||||||
@@ -160,36 +161,40 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.uoms.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteUomClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/uom/detail/edit/?uomId=${initialValues?.id}`}
|
onClick={deleteUomClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.uoms.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/uom/detail/edit/?uomId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||||
import { WarehouseApi } from '@/services/api/master-data';
|
import { WarehouseApi } from '@/services/api/master-data';
|
||||||
@@ -39,40 +40,46 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.warehouses.detail'>
|
||||||
href={`/master-data/warehouse/detail/?warehouseId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/master-data/warehouse/detail/?warehouseId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
href={`/master-data/warehouse/detail/edit/?warehouseId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='text-error hover:text-inherit'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.warehouses.update'>
|
||||||
|
<Button
|
||||||
|
href={`/master-data/warehouse/detail/edit/?warehouseId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.master.warehouses.delete'>
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='text-error hover:text-inherit'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -270,15 +277,17 @@ const WarehousesTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-row'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.warehouses.create'>
|
||||||
href='/master-data/warehouse/add'
|
<Button
|
||||||
variant='outline'
|
href='/master-data/warehouse/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import TextInput from '@/components/input/TextInput';
|
|||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
WarehouseFormSchema,
|
WarehouseFormSchema,
|
||||||
@@ -435,36 +436,40 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
|||||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.master.warehouses.delete'>
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={deleteWarehouseClickHandler}
|
|
||||||
className='px-4'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{type !== 'edit' && (
|
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='error'
|
||||||
href={`/master-data/warehouse/detail/edit/?warehouseId=${initialValues?.id}`}
|
onClick={deleteWarehouseClickHandler}
|
||||||
className='px-4'
|
className='px-4'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:edit-outline'
|
icon='material-symbols:delete-outline-rounded'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
/>
|
||||||
Edit
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{type !== 'edit' && (
|
||||||
|
<RequirePermission permissions='lti.master.warehouses.update'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='warning'
|
||||||
|
href={`/master-data/warehouse/detail/edit/?warehouseId=${initialValues?.id}`}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:edit-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,324 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import Button from '@/components/Button';
|
|
||||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
|
||||||
import { OptionType } from '@/components/input/SelectInput';
|
|
||||||
import Modal, { useModal } from '@/components/Modal';
|
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
|
||||||
import Table from '@/components/Table';
|
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
|
||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
|
||||||
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
|
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
|
||||||
import { cn, formatNumber } from '@/lib/helper';
|
|
||||||
import { ChickinApi } from '@/services/api/production/chickin';
|
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
|
||||||
import { Chickin } from '@/types/api/production/chickin';
|
|
||||||
import { Icon } from '@iconify/react';
|
|
||||||
import { CellContext, SortingState } from '@tanstack/react-table';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
|
|
||||||
const ChickinTable = () => {
|
|
||||||
const {
|
|
||||||
state: tableFilterState,
|
|
||||||
updateFilter,
|
|
||||||
setPage,
|
|
||||||
setPageSize,
|
|
||||||
toQueryString: getTableFilterQueryString,
|
|
||||||
} = useTableFilter({
|
|
||||||
initial: {
|
|
||||||
search: '',
|
|
||||||
},
|
|
||||||
paramMap: {
|
|
||||||
page: 'page',
|
|
||||||
pageSize: 'limit',
|
|
||||||
search: 'search',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
|
||||||
const [selectedChickin, setSelectedChickin] = useState<Chickin | undefined>(
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
|
||||||
|
|
||||||
const deleteModal = useModal();
|
|
||||||
const chickinModal = useModal();
|
|
||||||
|
|
||||||
// Data Fetching
|
|
||||||
const {
|
|
||||||
data: chickins,
|
|
||||||
isLoading,
|
|
||||||
mutate: refreshChickins,
|
|
||||||
} = useSWR(
|
|
||||||
`${ChickinApi.basePath}${getTableFilterQueryString()}`,
|
|
||||||
ChickinApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateFilter('search', event.target.value);
|
|
||||||
setPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
|
||||||
const newVal = val as OptionType;
|
|
||||||
setPageSize(newVal.value as number);
|
|
||||||
setPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
|
||||||
setIsDeleteLoading(true);
|
|
||||||
try {
|
|
||||||
await ChickinApi.delete(selectedChickin?.id as number);
|
|
||||||
refreshChickins();
|
|
||||||
deleteModal.closeModal();
|
|
||||||
} finally {
|
|
||||||
setIsDeleteLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className='flex flex-col gap-4'>
|
|
||||||
<div className='flex flex-col gap-2 mb-4'>
|
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
|
||||||
<Button
|
|
||||||
href='/production/project-flock/chickin/add?projectFlockId=1'
|
|
||||||
variant='outline'
|
|
||||||
color='primary'
|
|
||||||
className='w-full sm:w-fit'
|
|
||||||
>
|
|
||||||
<Icon icon='uil:plus' width={24} height={24} />
|
|
||||||
Tambah
|
|
||||||
</Button>
|
|
||||||
<DebouncedTextInput
|
|
||||||
name='search'
|
|
||||||
placeholder='Cari Chickin'
|
|
||||||
value={tableFilterState.search}
|
|
||||||
onChange={searchChangeHandler}
|
|
||||||
className={{ wrapper: 'sm:max-w-3xs' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<TableRowSizeSelector
|
|
||||||
value={tableFilterState.pageSize}
|
|
||||||
onChange={pageSizeChangeHandler}
|
|
||||||
options={ROWS_OPTIONS}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Table<Chickin>
|
|
||||||
data={isResponseSuccess(chickins) ? chickins?.data : []}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
header: '#',
|
|
||||||
cell: (props) =>
|
|
||||||
tableFilterState.pageSize * (tableFilterState.page - 1) +
|
|
||||||
props.row.index +
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row.project_flock_kandang?.kandang.name,
|
|
||||||
header: 'Kandang',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row.quantity,
|
|
||||||
header: 'Jumlah Chickin',
|
|
||||||
cell: (props) => {
|
|
||||||
if (props.row.original.quantity) {
|
|
||||||
return formatNumber(props.row.original.quantity);
|
|
||||||
} else {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row.chick_in_date,
|
|
||||||
header: 'Tanggal Chickin',
|
|
||||||
cell: (props) => {
|
|
||||||
if (props.row.original.chick_in_date) {
|
|
||||||
return new Date(
|
|
||||||
props.row.original.chick_in_date
|
|
||||||
).toLocaleDateString('id-ID');
|
|
||||||
} else {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row.note,
|
|
||||||
header: 'Catatan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Aksi',
|
|
||||||
cell: (props) => {
|
|
||||||
const currentPageSize =
|
|
||||||
props.table.getPaginationRowModel().rows.length;
|
|
||||||
const currentPageRows =
|
|
||||||
props.table.getPaginationRowModel().flatRows;
|
|
||||||
const currentRowRelativeIndex =
|
|
||||||
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
|
||||||
|
|
||||||
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
|
||||||
|
|
||||||
const deleteClickHandler = () => {
|
|
||||||
setSelectedChickin(props.row.original);
|
|
||||||
deleteModal.openModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
const editClickHandler = () => {
|
|
||||||
setSelectedChickin(props.row.original);
|
|
||||||
chickinModal.openModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{currentPageSize > 2 && (
|
|
||||||
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
|
||||||
<RowOptionsMenu
|
|
||||||
type='dropdown'
|
|
||||||
props={props}
|
|
||||||
deleteClickHandler={deleteClickHandler}
|
|
||||||
editClickHandler={editClickHandler}
|
|
||||||
/>
|
|
||||||
</RowDropdownOptions>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentPageSize <= 2 && (
|
|
||||||
<RowCollapseOptions>
|
|
||||||
<RowOptionsMenu
|
|
||||||
type='collapse'
|
|
||||||
props={props}
|
|
||||||
deleteClickHandler={deleteClickHandler}
|
|
||||||
editClickHandler={editClickHandler}
|
|
||||||
/>
|
|
||||||
</RowCollapseOptions>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
pageSize={tableFilterState.pageSize}
|
|
||||||
page={isResponseSuccess(chickins) ? chickins?.meta?.page : 0}
|
|
||||||
totalItems={
|
|
||||||
isResponseSuccess(chickins) ? chickins?.meta?.total_results : 0
|
|
||||||
}
|
|
||||||
onPageChange={setPage}
|
|
||||||
isLoading={isLoading}
|
|
||||||
sorting={sorting}
|
|
||||||
setSorting={setSorting}
|
|
||||||
className={{
|
|
||||||
containerClassName: cn({
|
|
||||||
'mb-20':
|
|
||||||
isResponseSuccess(chickins) && chickins?.data?.length === 0,
|
|
||||||
}),
|
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
|
||||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
|
||||||
headerColumnClassName:
|
|
||||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
|
||||||
bodyRowClassName: 'border-b border-b-gray-200',
|
|
||||||
bodyColumnClassName:
|
|
||||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ConfirmationModal
|
|
||||||
ref={deleteModal.ref}
|
|
||||||
type='error'
|
|
||||||
text={`Apakah anda yakin ingin menghapus data Chickin ini?`}
|
|
||||||
secondaryButton={{
|
|
||||||
text: 'Tidak',
|
|
||||||
}}
|
|
||||||
primaryButton={{
|
|
||||||
text: 'Ya',
|
|
||||||
onClick: confirmationModalDeleteClickHandler,
|
|
||||||
isLoading: isDeleteLoading,
|
|
||||||
color: 'error',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Modal ref={chickinModal.ref}>
|
|
||||||
<div className='flex flex-row justify-between items-center'>
|
|
||||||
<h1 className='text-xl font-semibold text-center mb-6'>
|
|
||||||
Chickin Kandang -{' '}
|
|
||||||
{selectedChickin?.project_flock_kandang &&
|
|
||||||
selectedChickin?.project_flock_kandang.kandang?.name}
|
|
||||||
</h1>
|
|
||||||
<Button
|
|
||||||
color='error'
|
|
||||||
variant='link'
|
|
||||||
onClick={chickinModal.closeModal}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className='text-black'
|
|
||||||
icon='uil:times'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/* <ChickinForm
|
|
||||||
initialValues={selectedChickin}
|
|
||||||
formType='edit'
|
|
||||||
afterSubmit={() => {
|
|
||||||
refreshChickins();
|
|
||||||
chickinModal.closeModal();
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
|
||||||
type = 'dropdown',
|
|
||||||
props,
|
|
||||||
editClickHandler,
|
|
||||||
deleteClickHandler,
|
|
||||||
}: {
|
|
||||||
type: 'dropdown' | 'collapse';
|
|
||||||
props: CellContext<Chickin, unknown>;
|
|
||||||
editClickHandler: () => void;
|
|
||||||
deleteClickHandler: () => void;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<RowOptionsMenuWrapper type={type}>
|
|
||||||
<Button
|
|
||||||
href={`/production/project-flock/chickin/detail?chickinId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='primary'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
onClick={editClickHandler}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</RowOptionsMenuWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ChickinTable;
|
|
||||||
@@ -17,6 +17,7 @@ import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line';
|
import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
const ChickinFormKandang = ({
|
const ChickinFormKandang = ({
|
||||||
formType = 'add',
|
formType = 'add',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -144,17 +145,24 @@ const ChickinFormKandang = ({
|
|||||||
<h2 className='text-xl font-semibold'>Informasi Chick In</h2>
|
<h2 className='text-xl font-semibold'>Informasi Chick In</h2>
|
||||||
{/* Badge Row */}
|
{/* Badge Row */}
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
<Badge
|
<RequirePermission permissions='lti.production.chickins.create'>
|
||||||
variant='soft'
|
<Badge
|
||||||
color={'success'}
|
variant='soft'
|
||||||
className={{
|
color={'success'}
|
||||||
badge: 'rounded-lg px-2',
|
className={{
|
||||||
}}
|
badge: 'rounded-lg px-2',
|
||||||
>
|
}}
|
||||||
<Icon icon='mdi:circle' width={12} height={12} color={'success'} />{' '}
|
>
|
||||||
Perlu Chick In ({initialValues.available_qtys?.length ?? 0})
|
<Icon
|
||||||
</Badge>
|
icon='mdi:circle'
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
width={12}
|
||||||
|
height={12}
|
||||||
|
color={'success'}
|
||||||
|
/>{' '}
|
||||||
|
Perlu Chick In ({initialValues.available_qtys?.length ?? 0})
|
||||||
|
</Badge>
|
||||||
|
<div className='divider divider-horizontal p-0 m-0'></div>
|
||||||
|
</RequirePermission>
|
||||||
<Badge
|
<Badge
|
||||||
color='neutral'
|
color='neutral'
|
||||||
variant='soft'
|
variant='soft'
|
||||||
@@ -176,11 +184,13 @@ const ChickinFormKandang = ({
|
|||||||
afterSubmit={afterSubmitFormChickin}
|
afterSubmit={afterSubmitFormChickin}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ChickinFormView
|
<RequirePermission permissions='lti.production.chickins.create'>
|
||||||
initialValues={initialValues}
|
<ChickinFormView
|
||||||
formType={formType}
|
initialValues={initialValues}
|
||||||
afterSubmit={afterSubmitFormChickin}
|
formType={formType}
|
||||||
/>
|
afterSubmit={afterSubmitFormChickin}
|
||||||
|
/>
|
||||||
|
</RequirePermission>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Alert from '@/components/Alert';
|
import Alert from '@/components/Alert';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
import PillBadge from '@/components/PillBadge';
|
import PillBadge from '@/components/PillBadge';
|
||||||
@@ -146,14 +147,16 @@ const ChickinLogsView = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{initialValues?.approval?.step_number <= 2 && (
|
{initialValues?.approval?.step_number <= 2 && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.chickins.approve'>
|
||||||
color='success'
|
<Button
|
||||||
onClick={handleClickApprove}
|
color='success'
|
||||||
className='w-full'
|
onClick={handleClickApprove}
|
||||||
>
|
className='w-full'
|
||||||
<Icon width={24} height={24} icon='material-symbols:check' />
|
>
|
||||||
Approve Semua Chick In
|
<Icon width={24} height={24} icon='material-symbols:check' />
|
||||||
</Button>
|
Approve Semua Chick In
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{chickinErrorMessage && (
|
{chickinErrorMessage && (
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import { ChangeEventHandler, useEffect, useMemo, useState } from 'react';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
props,
|
props,
|
||||||
@@ -46,50 +48,58 @@ const RowOptionsMenu = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.project_flocks.detail'>
|
||||||
href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='primary'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
{props.row.original.approval.step_name === 'Aktif' && (
|
|
||||||
<Button
|
<Button
|
||||||
href={`/production/project-flock/chickin/add?projectFlockId=${props.row.original.id}`}
|
href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='success'
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:home-import-outline' width={16} height={16} />
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
Chickin
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
{props.row.original.approval.step_name === 'Aktif' && (
|
||||||
|
<RequirePermission permissions='lti.production.chickins.create'>
|
||||||
|
<Button
|
||||||
|
href={`/production/project-flock/chickin/add?projectFlockId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='success'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:home-import-outline' width={16} height={16} />
|
||||||
|
Chickin
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
{props.row.original.approval.step_name === 'Pengajuan' && (
|
{props.row.original.approval.step_name === 'Pengajuan' && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.project_flocks.update'>
|
||||||
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
|
||||||
color='warning'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='warning'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
>
|
||||||
Edit
|
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
<Button
|
<RequirePermission permissions='lti.production.project_flocks.delete'>
|
||||||
onClick={deleteClickHandler}
|
<Button
|
||||||
variant='ghost'
|
onClick={deleteClickHandler}
|
||||||
color='error'
|
variant='ghost'
|
||||||
className='text-error hover:text-inherit justify-start text-sm'
|
color='error'
|
||||||
>
|
className='text-error hover:text-inherit justify-start text-sm'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<Icon
|
||||||
width={16}
|
icon='material-symbols:delete-outline-rounded'
|
||||||
height={16}
|
width={16}
|
||||||
/>
|
height={16}
|
||||||
Delete
|
/>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -283,44 +293,20 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='min-h-screen w-full p-0 sm:p-4'>
|
<div className='min-h-screen w-full p-4'>
|
||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col justify-between items-end gap-2'>
|
<div className='w-full flex flex-col justify-between items-end gap-2'>
|
||||||
<div className='flex flex-col sm:flex-row gap-3 w-full'>
|
<div className='flex flex-col sm:flex-row gap-3 w-full'>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.project_flocks.create'>
|
||||||
color='primary'
|
<Button
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
href='/production/project-flock/add'
|
className='w-full sm:w-fit'
|
||||||
>
|
href='/production/project-flock/add'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
{/* <Button
|
</Button>
|
||||||
variant='outline'
|
</RequirePermission>
|
||||||
color='success'
|
|
||||||
onClick={() => {
|
|
||||||
setApprovalAction('APPROVED');
|
|
||||||
confirmModal.openModal();
|
|
||||||
}}
|
|
||||||
disabled={selectedRowIds.length === 0}
|
|
||||||
className='w-full sm:w-fit'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
|
||||||
Approve
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant='outline'
|
|
||||||
color='error'
|
|
||||||
onClick={() => {
|
|
||||||
setApprovalAction('REJECTED');
|
|
||||||
confirmModal.openModal();
|
|
||||||
}}
|
|
||||||
disabled={selectedRowIds.length === 0}
|
|
||||||
className='w-full sm:w-fit'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:times' width={24} height={24} />
|
|
||||||
Reject
|
|
||||||
</Button> */}
|
|
||||||
<div className='ms-auto w-full sm:w-auto'>
|
<div className='ms-auto w-full sm:w-auto'>
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
name='search'
|
name='search'
|
||||||
@@ -539,49 +525,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
cell: (props) =>
|
cell: (props) =>
|
||||||
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
|
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// header: 'Aksi',
|
|
||||||
// cell: (props) => {
|
|
||||||
// const currentPageSize =
|
|
||||||
// props.table.getPaginationRowModel().rows.length;
|
|
||||||
// const currentPageRows =
|
|
||||||
// props.table.getPaginationRowModel().flatRows;
|
|
||||||
// const currentRowRelativeIndex =
|
|
||||||
// currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
|
||||||
|
|
||||||
// const isLast2Rows =
|
|
||||||
// currentRowRelativeIndex > currentPageSize - 2;
|
|
||||||
|
|
||||||
// const deleteClickHandler = () => {
|
|
||||||
// setSelectedProjectFlock(props.row.original);
|
|
||||||
// deleteModal.openModal();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <>
|
|
||||||
// {currentPageSize > 2 && (
|
|
||||||
// <RowDropdownOptions isLast2Rows={isLast2Rows}>
|
|
||||||
// <RowOptionsMenu
|
|
||||||
// type='dropdown'
|
|
||||||
// props={props}
|
|
||||||
// deleteClickHandler={deleteClickHandler}
|
|
||||||
// />
|
|
||||||
// </RowDropdownOptions>
|
|
||||||
// )}
|
|
||||||
|
|
||||||
// {currentPageSize <= 2 && (
|
|
||||||
// <RowCollapseOptions>
|
|
||||||
// <RowOptionsMenu
|
|
||||||
// type='collapse'
|
|
||||||
// props={props}
|
|
||||||
// deleteClickHandler={deleteClickHandler}
|
|
||||||
// />
|
|
||||||
// </RowCollapseOptions>
|
|
||||||
// )}
|
|
||||||
// </>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
]}
|
]}
|
||||||
pageSize={tableFilterState.pageSize}
|
pageSize={tableFilterState.pageSize}
|
||||||
page={
|
page={
|
||||||
@@ -630,6 +573,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
);
|
);
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
},
|
},
|
||||||
|
permissions: 'lti.production.project_flocks.detail',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'DELETE',
|
action: 'DELETE',
|
||||||
@@ -639,6 +583,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
deleteModal.openModal();
|
deleteModal.openModal();
|
||||||
},
|
},
|
||||||
|
permissions: 'lti.production.project_flocks.delete',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
approvals={[
|
approvals={[
|
||||||
@@ -651,6 +596,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
confirmModal.openModal();
|
confirmModal.openModal();
|
||||||
},
|
},
|
||||||
disabled: !canApprove,
|
disabled: !canApprove,
|
||||||
|
permissions: 'lti.production.project_flocks.approve',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'mdi:times',
|
icon: 'mdi:times',
|
||||||
@@ -660,6 +606,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
setApprovalAction('REJECTED');
|
setApprovalAction('REJECTED');
|
||||||
confirmModal.openModal();
|
confirmModal.openModal();
|
||||||
},
|
},
|
||||||
|
permissions: 'lti.production.project_flocks.approve',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
selectedRowIds={selectedRowIds}
|
selectedRowIds={selectedRowIds}
|
||||||
|
|||||||
@@ -1,640 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import Badge from '@/components/Badge';
|
|
||||||
import Button from '@/components/Button';
|
|
||||||
import Card from '@/components/Card';
|
|
||||||
import SelectInput, {
|
|
||||||
OptionType,
|
|
||||||
useSelect,
|
|
||||||
} from '@/components/input/SelectInput';
|
|
||||||
import PillBadge from '@/components/PillBadge';
|
|
||||||
import Table from '@/components/Table';
|
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
|
||||||
import { cn, formatDate, formatTitleCase } from '@/lib/helper';
|
|
||||||
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
|
||||||
import { ProjectFlockKandangApi } from '@/services/api/production';
|
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
|
||||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
|
||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
|
||||||
import { Icon } from '@iconify/react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
const ProjectFlockChickinDetail = ({
|
|
||||||
projectFlockId,
|
|
||||||
}: {
|
|
||||||
projectFlockId: number | undefined;
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Tables Props
|
|
||||||
const { state: tableFilterState } = useTableFilter({
|
|
||||||
initial: { search: '' },
|
|
||||||
paramMap: { page: 'page', pageSize: 'limit' },
|
|
||||||
});
|
|
||||||
|
|
||||||
// States
|
|
||||||
const [searchProjectFlock, setSearchProjectFlock] = useState('');
|
|
||||||
const [selectedProjectFlock, setSelectedProjectFlock] =
|
|
||||||
useState<OptionType | null>(null);
|
|
||||||
const [projectFlock, setProjectFlock] = useState<ProjectFlock>();
|
|
||||||
|
|
||||||
// Fetch Data
|
|
||||||
const { data: listProjectFlockKandang } = useSWR(
|
|
||||||
`${ProjectFlockKandangApi.basePath}?${new URLSearchParams({
|
|
||||||
search: searchProjectFlock,
|
|
||||||
project_flock_id:
|
|
||||||
projectFlock?.id?.toString() ?? projectFlockId?.toString() ?? '',
|
|
||||||
}).toString()}`,
|
|
||||||
ProjectFlockKandangApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
options: options,
|
|
||||||
isLoadingOptions: isLoadingListProjectFlock,
|
|
||||||
rawData: listProjectFlock,
|
|
||||||
} = useSelect<ProjectFlock>(
|
|
||||||
ProjectFlockApi.basePath,
|
|
||||||
'id',
|
|
||||||
'flock_name',
|
|
||||||
'',
|
|
||||||
{
|
|
||||||
search: searchProjectFlock,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle Function
|
|
||||||
const handleChickinClick = async (
|
|
||||||
projectFlockKandang: ProjectFlockKandang
|
|
||||||
) => {
|
|
||||||
router.push(
|
|
||||||
`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${projectFlockKandang.id}&projectFlockId=${projectFlockId ?? selectedProjectFlock?.value}`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeProjectFlock = (val: OptionType | null) => {
|
|
||||||
setSelectedProjectFlock(val);
|
|
||||||
if (isResponseSuccess(listProjectFlock) && val) {
|
|
||||||
const selected = listProjectFlock.data.find(
|
|
||||||
(pf) => pf.id === Number(val.value)
|
|
||||||
);
|
|
||||||
setProjectFlock(selected);
|
|
||||||
} else {
|
|
||||||
setProjectFlock(undefined);
|
|
||||||
}
|
|
||||||
if (projectFlockId) {
|
|
||||||
router.push('/production/project-flock/chickin/add');
|
|
||||||
}
|
|
||||||
if (!val && projectFlockId) {
|
|
||||||
router.push('/production/project-flock/chickin/add');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (projectFlockId && isResponseSuccess(listProjectFlock)) {
|
|
||||||
setProjectFlock(
|
|
||||||
listProjectFlock.data.find((pf) => pf.id === Number(projectFlockId))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [projectFlockId, listProjectFlock]);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* Header */}
|
|
||||||
<div className='flex flex-row justify-between items-center px-4 py-4'>
|
|
||||||
<div className='flex flex-row items-center h-full gap-2'>
|
|
||||||
<Link
|
|
||||||
href={`/production/project-flock/detail?projectFlockId=${projectFlock?.id}`}
|
|
||||||
className='hover:text-gray-400'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:arrow-left' width={24} height={24} />
|
|
||||||
</Link>
|
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
|
||||||
<div className='text-sm text-neutral'>
|
|
||||||
Chick In {projectFlock?.flock_name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <FormHeader
|
|
||||||
title={`Chick In ${projectFlock?.flock_name ?? 'Project Flock'}`}
|
|
||||||
backUrl={`/production/project-flock/detail?projectFlockId=${projectFlock?.id}`}
|
|
||||||
/> */}
|
|
||||||
{/* <div className='flex flex-col gap-4 w-full my-4'>
|
|
||||||
<div className='max-w-full sm:max-w-1/2 md:max-w-3/5 lg:max-w-2/5'>
|
|
||||||
<SelectInput
|
|
||||||
required
|
|
||||||
label='Ganti Project Flock'
|
|
||||||
placeholder='Pilih Project Flock'
|
|
||||||
options={options}
|
|
||||||
onInputChange={(val) => {
|
|
||||||
setSearchProjectFlock(val);
|
|
||||||
}}
|
|
||||||
isLoading={isLoadingListProjectFlock}
|
|
||||||
value={
|
|
||||||
projectFlock
|
|
||||||
? {
|
|
||||||
label: `${projectFlock?.flock_name}`,
|
|
||||||
value: projectFlock?.id,
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
onChange={(val) => {
|
|
||||||
handleChangeProjectFlock(val as OptionType | null);
|
|
||||||
}}
|
|
||||||
isSearchable
|
|
||||||
isClearable
|
|
||||||
startAdornment={
|
|
||||||
projectFlock && (
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color='success'
|
|
||||||
size='sm'
|
|
||||||
className={{
|
|
||||||
badge: 'whitespace-nowrap font-semibold',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Periode {projectFlock?.period}
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
{/* Informasi Umum */}
|
|
||||||
{projectFlock && (
|
|
||||||
<div className='border-t-1 border-gray-300'>
|
|
||||||
<div className='p-4 flex flex-col gap-4'>
|
|
||||||
<h2 className='text-2xl font-semibold'>Informasi Umum</h2>
|
|
||||||
{/* Badge Row */}
|
|
||||||
<div className='flex flex-row gap-2'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color={
|
|
||||||
projectFlock.approval.step_number == 1
|
|
||||||
? 'neutral'
|
|
||||||
: projectFlock.approval.step_number == 2
|
|
||||||
? 'success'
|
|
||||||
: projectFlock.approval.step_number >= 3
|
|
||||||
? 'error'
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:circle'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
color={
|
|
||||||
projectFlock.approval.step_number == 1
|
|
||||||
? 'neutral'
|
|
||||||
: projectFlock.approval.step_number == 2
|
|
||||||
? 'success'
|
|
||||||
: projectFlock.approval.step_number >= 3
|
|
||||||
? 'error'
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>{' '}
|
|
||||||
{projectFlock.approval.step_name}
|
|
||||||
</Badge>
|
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
|
||||||
<Badge
|
|
||||||
color='neutral'
|
|
||||||
variant='soft'
|
|
||||||
className={{ badge: 'rounded-lg px-2' }}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:bookmark' width={12} height={12} />
|
|
||||||
{` ${formatTitleCase(projectFlock.category)}`}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
{/* Information Grid */}
|
|
||||||
<div className='grid grid-cols-3 gap-4'>
|
|
||||||
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
|
|
||||||
<Icon width={14} height={14} icon='mdi:account' /> Submitted
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color='neutral'
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:account-circle' width={14} height={14} />{' '}
|
|
||||||
{projectFlock.created_user.name}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
|
|
||||||
<Icon width={14} height={14} icon={'mdi:clock'} /> History
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
<Button variant='outline' className='py-1 text-sm'>
|
|
||||||
See History{' '}
|
|
||||||
<Icon
|
|
||||||
icon='mdi:arrow-top-right-thin'
|
|
||||||
width={11}
|
|
||||||
height={11}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* BARIS 1 */}
|
|
||||||
<div
|
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
|
||||||
relative
|
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>{projectFlock.area.name}</div>
|
|
||||||
|
|
||||||
{/* BARIS 2 */}
|
|
||||||
<div
|
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
|
||||||
relative
|
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>{projectFlock.location.name}</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
|
||||||
relative
|
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> FCR
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>{projectFlock.fcr.name}</div>
|
|
||||||
|
|
||||||
{/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */}
|
|
||||||
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '}
|
|
||||||
Kategori
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
{formatTitleCase(projectFlock.category)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* <Card
|
|
||||||
title='Informasi Flock'
|
|
||||||
className={{
|
|
||||||
wrapper: 'w-full bg-white mb-3',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Table<ProjectFlock>
|
|
||||||
emptyContent={
|
|
||||||
<div className='w-full p-5 text-center'>
|
|
||||||
<span className='text-lg opacity-50'>
|
|
||||||
Pilih project flock terlebih dahulu...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
data={projectFlock ? [projectFlock] : []}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
header: 'ID',
|
|
||||||
accessorKey: 'id',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Area',
|
|
||||||
accessorKey: 'area.name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Lokasi',
|
|
||||||
accessorKey: 'location.name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Nama Flock',
|
|
||||||
accessorKey: 'flock_name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Kategori',
|
|
||||||
accessorKey: 'category',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Status',
|
|
||||||
accessorKey: 'status',
|
|
||||||
cell: (props) => {
|
|
||||||
return props.row.original.approval?.step_name ? (
|
|
||||||
<PillBadge
|
|
||||||
color={(() => {
|
|
||||||
switch (
|
|
||||||
props.row.original.approval?.step_name.toUpperCase()
|
|
||||||
) {
|
|
||||||
case 'AKTIF':
|
|
||||||
return 'red';
|
|
||||||
case 'PENGAJUAN':
|
|
||||||
return 'green';
|
|
||||||
default:
|
|
||||||
return 'gray';
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
content={props.row.original.approval?.step_name
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/_/g, ' ')
|
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase())}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
'-'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'FCR Layer',
|
|
||||||
accessorKey: 'fcr.name',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
page={undefined}
|
|
||||||
className={{
|
|
||||||
containerClassName: cn({
|
|
||||||
'mb-20': projectFlock && projectFlock.kandangs?.length === 0,
|
|
||||||
}),
|
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
|
||||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
|
||||||
headerColumnClassName:
|
|
||||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
|
||||||
bodyRowClassName: 'border-b border-b-gray-200',
|
|
||||||
bodyColumnClassName:
|
|
||||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
|
||||||
paginationClassName: 'hidden',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Card> */}
|
|
||||||
{/* Card Kandangs */}
|
|
||||||
<div className='border-t-1 border-gray-300'>
|
|
||||||
<div className='p-4 flex flex-col gap-4'>
|
|
||||||
<h2 className='text-2xl font-semibold'>Daftar Kandang</h2>
|
|
||||||
{isResponseSuccess(listProjectFlock) ? (
|
|
||||||
<>
|
|
||||||
{/* Badge Row */}
|
|
||||||
<div className='flex flex-row gap-2'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color={'success'}
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:circle'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
color={'success'}
|
|
||||||
/>{' '}
|
|
||||||
Disetujui (
|
|
||||||
{isResponseSuccess(listProjectFlockKandang) &&
|
|
||||||
listProjectFlockKandang.data.filter(
|
|
||||||
(k) => k.approval?.step_number == 1
|
|
||||||
).length}
|
|
||||||
)
|
|
||||||
</Badge>
|
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color={'neutral'}
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:circle'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
color={'neutral'}
|
|
||||||
/>{' '}
|
|
||||||
Pengajuan (
|
|
||||||
{isResponseSuccess(listProjectFlockKandang) &&
|
|
||||||
listProjectFlockKandang.data.filter(
|
|
||||||
(k) => k.approval?.step_number == 2
|
|
||||||
).length}
|
|
||||||
)
|
|
||||||
</Badge>
|
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
|
||||||
<Badge
|
|
||||||
color='error'
|
|
||||||
variant='soft'
|
|
||||||
className={{ badge: 'rounded-lg px-2' }}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon={`mdi:circle`}
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
color='error'
|
|
||||||
/>
|
|
||||||
Belum Chickin (
|
|
||||||
{isResponseSuccess(listProjectFlockKandang) &&
|
|
||||||
listProjectFlockKandang.data.filter(
|
|
||||||
(k) => k.approval == null
|
|
||||||
).length}
|
|
||||||
)
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
{/* Card Kandang */}
|
|
||||||
<Card
|
|
||||||
variant='bordered'
|
|
||||||
className={{
|
|
||||||
wrapper: 'w-full',
|
|
||||||
body: 'p-3',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='flex flex-col gap-6'>
|
|
||||||
{isResponseSuccess(listProjectFlockKandang) &&
|
|
||||||
listProjectFlockKandang.data.map((kandang) => (
|
|
||||||
<div
|
|
||||||
key={kandang.id}
|
|
||||||
className='flex flex-row justify-between items-center'
|
|
||||||
>
|
|
||||||
<div className='flex flex-row gap-2 items-center cursor-pointer text-gray-400'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color={
|
|
||||||
kandang.approval
|
|
||||||
? kandang.approval.step_number == 1
|
|
||||||
? 'success'
|
|
||||||
: 'neutral'
|
|
||||||
: 'error'
|
|
||||||
}
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:circle'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
color={
|
|
||||||
kandang.approval
|
|
||||||
? kandang.approval.step_number == 1
|
|
||||||
? 'success'
|
|
||||||
: 'neutral'
|
|
||||||
: 'gray'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Badge>
|
|
||||||
<span className='font-semibold'>
|
|
||||||
{kandang.kandang.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant='outline'
|
|
||||||
className='py-1 text-sm'
|
|
||||||
onClick={() => {
|
|
||||||
handleChickinClick(kandang);
|
|
||||||
}}
|
|
||||||
disabled={projectFlock?.approval?.step_number === 1}
|
|
||||||
>
|
|
||||||
Chick In{' '}
|
|
||||||
<Icon
|
|
||||||
icon='mdi:arrow-top-right-thin'
|
|
||||||
width={11}
|
|
||||||
height={11}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className='w-full p-5 text-center'>
|
|
||||||
<span className='text-lg opacity-50'>
|
|
||||||
Pilih project flock terlebih dahulu...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <Card
|
|
||||||
title='Daftar Kandang'
|
|
||||||
className={{
|
|
||||||
wrapper: 'w-full bg-white',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Table<ProjectFlockKandang>
|
|
||||||
emptyContent={
|
|
||||||
<div className='w-full p-5 text-center'>
|
|
||||||
<span className='text-lg opacity-50'>
|
|
||||||
Pilih project flock terlebih dahulu...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
data={
|
|
||||||
projectFlock && isResponseSuccess(listProjectFlockKandang)
|
|
||||||
? listProjectFlockKandang.data
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
header: '#',
|
|
||||||
cell: (props) =>
|
|
||||||
tableFilterState.pageSize * (tableFilterState.page - 1) +
|
|
||||||
props.row.index +
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row?.project_flock?.area?.name,
|
|
||||||
header: 'Area',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row?.project_flock?.location?.name,
|
|
||||||
header: 'Lokasi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'kandang.name',
|
|
||||||
header: 'Kandang',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'kandang.capacity',
|
|
||||||
header: 'Kapasitas',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: () => projectFlock?.period,
|
|
||||||
header: 'Periode',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'approval.step_name',
|
|
||||||
header: 'Status',
|
|
||||||
cell: (props) => {
|
|
||||||
return props.row.original.approval?.step_name ? (
|
|
||||||
<PillBadge
|
|
||||||
color={(() => {
|
|
||||||
switch (
|
|
||||||
props.row.original.approval?.step_name.toUpperCase()
|
|
||||||
) {
|
|
||||||
case 'DISETUJUI':
|
|
||||||
return 'green';
|
|
||||||
case 'PENGAJUAN':
|
|
||||||
return 'yellow';
|
|
||||||
default:
|
|
||||||
return 'gray';
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
content={props.row.original.approval?.step_name
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/_/g, ' ')
|
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase())}
|
|
||||||
/>
|
|
||||||
) : projectFlock?.approval?.step_number === 1 ? (
|
|
||||||
<PillBadge color='red' content={'Tidak Dapat Chick In'} />
|
|
||||||
) : (
|
|
||||||
<PillBadge color='gray' content={'Belum Chick In'} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Aksi',
|
|
||||||
cell: (props) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
color='success'
|
|
||||||
variant='outline'
|
|
||||||
onClick={() => {
|
|
||||||
handleChickinClick(props.row.original);
|
|
||||||
}}
|
|
||||||
className='p-1'
|
|
||||||
disabled={projectFlock?.approval?.step_number === 1}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:home-import-outline'
|
|
||||||
width={18}
|
|
||||||
height={18}
|
|
||||||
/>
|
|
||||||
Chickin
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
page={undefined}
|
|
||||||
className={{
|
|
||||||
containerClassName: cn({
|
|
||||||
'mb-20': projectFlock && projectFlock.kandangs?.length === 0,
|
|
||||||
}),
|
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
|
||||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
|
||||||
headerColumnClassName:
|
|
||||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
|
||||||
bodyRowClassName: 'border-b border-b-gray-200',
|
|
||||||
bodyColumnClassName:
|
|
||||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
|
||||||
paginationClassName: 'hidden',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Card> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectFlockChickinDetail;
|
|
||||||
@@ -22,6 +22,7 @@ import toast from 'react-hot-toast';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||||
import { ApprovalApi } from '@/services/api/approval';
|
import { ApprovalApi } from '@/services/api/approval';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
const ProjectFlockClosingForm = ({
|
const ProjectFlockClosingForm = ({
|
||||||
projectFlock,
|
projectFlock,
|
||||||
@@ -285,16 +286,18 @@ const ProjectFlockClosingForm = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='p-4 mt-6'>
|
<div className='p-4 mt-6'>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.project_flock_kandangs.closing'>
|
||||||
className='w-full'
|
<Button
|
||||||
color='error'
|
className='w-full'
|
||||||
isLoading={isLoading}
|
color='error'
|
||||||
disabled={!isCanCloseValid}
|
isLoading={isLoading}
|
||||||
onClick={() => closeModal.openModal()}
|
disabled={!isCanCloseValid}
|
||||||
>
|
onClick={() => closeModal.openModal()}
|
||||||
<Icon icon='mdi:checkbox-marked-circle-outline' />{' '}
|
>
|
||||||
{isCanClose ? 'Close' : 'Unclose'}
|
<Icon icon='mdi:checkbox-marked-circle-outline' />{' '}
|
||||||
</Button>
|
{isCanClose ? 'Close' : 'Unclose'}
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
} from '@/config/approval-line';
|
} from '@/config/approval-line';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { ProjectFlockKandangApi } from '@/services/api/production';
|
import { ProjectFlockKandangApi } from '@/services/api/production';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
const ProjectFlockDetail = ({
|
const ProjectFlockDetail = ({
|
||||||
projectFlock,
|
projectFlock,
|
||||||
@@ -110,27 +111,31 @@ const ProjectFlockDetail = ({
|
|||||||
leftIconHref='/production/project-flock'
|
leftIconHref='/production/project-flock'
|
||||||
subtitle={`Created On ${formatDate(projectFlock.created_at, 'MMM DD, YYYY')}`}
|
subtitle={`Created On ${formatDate(projectFlock.created_at, 'MMM DD, YYYY')}`}
|
||||||
>
|
>
|
||||||
<Link
|
<RequirePermission permissions='lti.production.project_flocks.update'>
|
||||||
href={`/production/project-flock/detail/edit?projectFlockId=${projectFlock.id}`}
|
<Link
|
||||||
className='p-0'
|
href={`/production/project-flock/detail/edit?projectFlockId=${projectFlock.id}`}
|
||||||
>
|
className='p-0'
|
||||||
<Tooltip content='Edit' position='bottom'>
|
>
|
||||||
<Button variant='link' className='p-0 text-neutral'>
|
<Tooltip content='Edit' position='bottom'>
|
||||||
<Icon icon='mdi:square-edit-outline' width={20} height={20} />
|
<Button variant='link' className='p-0 text-neutral'>
|
||||||
</Button>
|
<Icon icon='mdi:square-edit-outline' width={20} height={20} />
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Link>
|
</Tooltip>
|
||||||
<Button
|
</Link>
|
||||||
variant='link'
|
</RequirePermission>
|
||||||
className='p-0 text-error'
|
<RequirePermission permissions='lti.production.project_flocks.delete'>
|
||||||
onClick={() => {
|
<Button
|
||||||
deleteModal.openModal();
|
variant='link'
|
||||||
}}
|
className='p-0 text-error'
|
||||||
>
|
onClick={() => {
|
||||||
<Tooltip content='Hapus' position='bottom'>
|
deleteModal.openModal();
|
||||||
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
}}
|
||||||
</Tooltip>
|
>
|
||||||
</Button>
|
<Tooltip content='Hapus' position='bottom'>
|
||||||
|
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
|
|
||||||
{/* Informasi Umum */}
|
{/* Informasi Umum */}
|
||||||
@@ -418,38 +423,42 @@ const ProjectFlockDetail = ({
|
|||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</Card>
|
</Card>
|
||||||
<div className='grid grid-cols-4 gap-3'>
|
<div className='grid grid-cols-4 gap-3'>
|
||||||
<Link
|
<RequirePermission permissions='lti.production.chickins.detail'>
|
||||||
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
|
<Link
|
||||||
className='m-0 p-0'
|
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
|
||||||
>
|
className='m-0 p-0'
|
||||||
<Button
|
|
||||||
className='w-full px-2 py-1 text-sm'
|
|
||||||
variant='outline'
|
|
||||||
color='success'
|
|
||||||
disabled={
|
|
||||||
!selectedKandangId ||
|
|
||||||
projectFlock?.approval?.step_number == 1
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Chickin <Icon icon='mdi:checkbox-marked-outline' />
|
<Button
|
||||||
</Button>
|
className='w-full px-2 py-1 text-sm'
|
||||||
</Link>
|
variant='outline'
|
||||||
<Link
|
color='success'
|
||||||
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
|
disabled={
|
||||||
className='m-0 p-0'
|
!selectedKandangId ||
|
||||||
>
|
projectFlock?.approval?.step_number == 1
|
||||||
<Button
|
}
|
||||||
className='w-full px-2 py-1 text-sm'
|
>
|
||||||
variant='outline'
|
Chickin <Icon icon='mdi:checkbox-marked-outline' />
|
||||||
color='error'
|
</Button>
|
||||||
disabled={
|
</Link>
|
||||||
!selectedKandangId ||
|
</RequirePermission>
|
||||||
projectFlock?.approval?.step_number == 1
|
<RequirePermission permissions='lti.production.project_flock_kandangs.closing.detail'>
|
||||||
}
|
<Link
|
||||||
|
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
|
||||||
|
className='m-0 p-0'
|
||||||
>
|
>
|
||||||
Close <Icon icon='mdi:checkbox-marked-circle-outline' />
|
<Button
|
||||||
</Button>
|
className='w-full px-2 py-1 text-sm'
|
||||||
</Link>
|
variant='outline'
|
||||||
|
color='error'
|
||||||
|
disabled={
|
||||||
|
!selectedKandangId ||
|
||||||
|
projectFlock?.approval?.step_number == 1
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Close <Icon icon='mdi:checkbox-marked-circle-outline' />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import Card from '@/components/Card';
|
|||||||
import ProjectFlockKandangTable from '@/components/pages/production/project-flock/form/ProjectFlockKandangTable';
|
import ProjectFlockKandangTable from '@/components/pages/production/project-flock/form/ProjectFlockKandangTable';
|
||||||
import { Nonstock } from '@/types/api/master-data/nonstock';
|
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||||
import { useUiStore } from '@/stores/ui/ui.store';
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||||
|
|
||||||
interface ProjectFlockFormProps {
|
interface ProjectFlockFormProps {
|
||||||
@@ -734,36 +735,40 @@ const ProjectFlockForm = ({
|
|||||||
)}
|
)}
|
||||||
{formType == 'detail' && (
|
{formType == 'detail' && (
|
||||||
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
|
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.project_flocks.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='success'
|
variant='outline'
|
||||||
onClick={() => {
|
color='success'
|
||||||
if (initialValues?.id) {
|
onClick={() => {
|
||||||
setApprovalAction('APPROVED');
|
if (initialValues?.id) {
|
||||||
confirmModal.openModal();
|
setApprovalAction('APPROVED');
|
||||||
}
|
confirmModal.openModal();
|
||||||
}}
|
}
|
||||||
disabled={!initialValues?.id || isApprovedDisabled}
|
}}
|
||||||
className='w-full sm:w-fit'
|
disabled={!initialValues?.id || isApprovedDisabled}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
>
|
||||||
Approve
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
</Button>
|
Approve
|
||||||
<Button
|
</Button>
|
||||||
variant='outline'
|
</RequirePermission>
|
||||||
color='error'
|
<RequirePermission permissions='lti.production.project_flocks.approve'>
|
||||||
onClick={() => {
|
<Button
|
||||||
if (initialValues?.id) {
|
variant='outline'
|
||||||
setApprovalAction('REJECTED');
|
color='error'
|
||||||
confirmModal.openModal();
|
onClick={() => {
|
||||||
}
|
if (initialValues?.id) {
|
||||||
}}
|
setApprovalAction('REJECTED');
|
||||||
disabled={!initialValues?.id || isRejectedDisabled}
|
confirmModal.openModal();
|
||||||
className='w-full sm:w-fit'
|
}
|
||||||
>
|
}}
|
||||||
<Icon icon='mdi:times' width={24} height={24} />
|
disabled={!initialValues?.id || isRejectedDisabled}
|
||||||
Reject
|
className='w-full sm:w-fit'
|
||||||
</Button>
|
>
|
||||||
|
<Icon icon='mdi:times' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<form
|
<form
|
||||||
@@ -1127,16 +1132,24 @@ const ProjectFlockForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div> */}
|
||||||
{formType !== 'detail' && (
|
{formType !== 'detail' && (
|
||||||
<Button
|
<RequirePermission
|
||||||
type='submit'
|
permissions={
|
||||||
color='primary'
|
formType == 'add'
|
||||||
isLoading={formik.isSubmitting}
|
? 'lti.production.project_flocks.create'
|
||||||
disabled={!formik.isValid || formik.isSubmitting}
|
: 'lti.production.project_flocks.update'
|
||||||
className='px-4 w-full'
|
}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:plus' width={24} height={24} />
|
<Button
|
||||||
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
|
type='submit'
|
||||||
</Button>
|
color='primary'
|
||||||
|
isLoading={formik.isSubmitting}
|
||||||
|
disabled={!formik.isValid || formik.isSubmitting}
|
||||||
|
className='px-4 w-full'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:plus' width={24} height={24} />
|
||||||
|
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { SortingState, CellContext } from '@tanstack/react-table';
|
import { SortingState, CellContext } from '@tanstack/react-table';
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
@@ -59,60 +60,70 @@ const RowOptionsMenu = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.detail'>
|
||||||
href={`/production/recording/detail/?recordingId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='primary'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
href={`/production/recording/detail/edit/?recordingId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='warning'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
{!isApproved && !isRejected && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={approveClickHandler}
|
href={`/production/recording/detail/?recordingId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='success'
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:check' width={16} height={16} />
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
Approve
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
<RequirePermission permissions='lti.production.recording.update'>
|
||||||
|
<Button
|
||||||
|
href={`/production/recording/detail/edit/?recordingId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
{!isApproved && !isRejected && (
|
||||||
|
<RequirePermission permissions='lti.production.recording.approve'>
|
||||||
|
<Button
|
||||||
|
onClick={approveClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='success'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:check' width={16} height={16} />
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
{!isApproved && !isRejected && (
|
{!isApproved && !isRejected && (
|
||||||
|
<RequirePermission permissions='lti.production.recording.approve'>
|
||||||
|
<Button
|
||||||
|
onClick={rejectClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:close' width={16} height={16} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
<RequirePermission permissions='lti.production.recording.delete'>
|
||||||
<Button
|
<Button
|
||||||
onClick={rejectClickHandler}
|
onClick={deleteClickHandler}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='error'
|
color='error'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:close' width={16} height={16} />
|
<Icon
|
||||||
Reject
|
icon='mdi:delete-outline'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</RequirePermission>
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:delete-outline'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -514,49 +525,63 @@ const RecordingTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
||||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.create'>
|
||||||
href='/production/recording/add'
|
<Button
|
||||||
variant='outline'
|
href='/production/recording/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
{selectedRowIds.length > 0 && (
|
{selectedRowIds.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='success'
|
variant='outline'
|
||||||
onClick={() => {
|
color='success'
|
||||||
setApprovalNotes('');
|
onClick={() => {
|
||||||
approveModal.openModal();
|
setApprovalNotes('');
|
||||||
}}
|
approveModal.openModal();
|
||||||
disabled={
|
}}
|
||||||
selectedRowIds.length === 0 || eligibleRowIds.length === 0
|
disabled={
|
||||||
}
|
selectedRowIds.length === 0 || eligibleRowIds.length === 0
|
||||||
className='w-full sm:w-fit'
|
}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
>
|
||||||
Approve
|
<Icon
|
||||||
</Button>
|
icon='material-symbols:check'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='error'
|
variant='outline'
|
||||||
onClick={() => {
|
color='error'
|
||||||
setApprovalNotes('');
|
onClick={() => {
|
||||||
rejectModal.openModal();
|
setApprovalNotes('');
|
||||||
}}
|
rejectModal.openModal();
|
||||||
disabled={
|
}}
|
||||||
selectedRowIds.length === 0 || eligibleRowIds.length === 0
|
disabled={
|
||||||
}
|
selectedRowIds.length === 0 || eligibleRowIds.length === 0
|
||||||
className='w-full sm:w-fit'
|
}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
>
|
||||||
Reject
|
<Icon
|
||||||
</Button>
|
icon='material-symbols:close'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import useSWR from 'swr';
|
|||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import NumberInput from '@/components/input/NumberInput';
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
@@ -1492,41 +1493,45 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
!isRecordingApproved(initialValues) &&
|
!isRecordingApproved(initialValues) &&
|
||||||
!isRecordingRejected(initialValues) && (
|
!isRecordingRejected(initialValues) && (
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='success'
|
variant='outline'
|
||||||
onClick={() => {
|
color='success'
|
||||||
setApprovalNotes('');
|
onClick={() => {
|
||||||
approveModal.openModal();
|
setApprovalNotes('');
|
||||||
}}
|
approveModal.openModal();
|
||||||
isLoading={isApproveLoading}
|
}}
|
||||||
className='w-full sm:w-fit'
|
isLoading={isApproveLoading}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:check'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:check'
|
||||||
height={24}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
Approve
|
/>
|
||||||
</Button>
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='error'
|
variant='outline'
|
||||||
onClick={() => {
|
color='error'
|
||||||
setApprovalNotes('');
|
onClick={() => {
|
||||||
rejectModal.openModal();
|
setApprovalNotes('');
|
||||||
}}
|
rejectModal.openModal();
|
||||||
isLoading={isRejectLoading}
|
}}
|
||||||
className='w-full sm:w-fit'
|
isLoading={isRejectLoading}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:close'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:close'
|
||||||
height={24}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
Reject
|
/>
|
||||||
</Button>
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -2696,36 +2701,40 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
{/* Left side - Detail & Edit actions */}
|
{/* Left side - Detail & Edit actions */}
|
||||||
<div className='flex flex-col sm:flex-row justify-start gap-2 w-full sm:w-auto'>
|
<div className='flex flex-col sm:flex-row justify-start gap-2 w-full sm:w-auto'>
|
||||||
{type === 'detail' && deleteRecordingClickHandler && (
|
{type === 'detail' && deleteRecordingClickHandler && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.delete'>
|
||||||
type='button'
|
<Button
|
||||||
color='error'
|
type='button'
|
||||||
onClick={deleteRecordingClickHandler}
|
color='error'
|
||||||
className='px-4'
|
onClick={deleteRecordingClickHandler}
|
||||||
>
|
className='px-4'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:delete-outline-rounded'
|
||||||
height={24}
|
width={24}
|
||||||
className='justify-start text-sm'
|
height={24}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Delete
|
/>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
{type === 'detail' && initialValues && (
|
{type === 'detail' && initialValues && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.recording.update'>
|
||||||
type='button'
|
<Button
|
||||||
color='warning'
|
type='button'
|
||||||
href={`/production/recording/detail/edit/?recordingId=${initialValues.id}`}
|
color='warning'
|
||||||
className='px-4'
|
href={`/production/recording/detail/edit/?recordingId=${initialValues.id}`}
|
||||||
>
|
className='px-4'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:edit-outline'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:edit-outline'
|
||||||
height={24}
|
width={24}
|
||||||
className='justify-start text-sm'
|
height={24}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Edit
|
/>
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Right side actions */}
|
{/* Right side actions */}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import TextInput from '@/components/input/TextInput';
|
|||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
|
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
|
||||||
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||||
@@ -56,72 +57,81 @@ const RowOptionsMenu = ({
|
|||||||
|
|
||||||
const showDeleteButton = showEditButton;
|
const showDeleteButton = showEditButton;
|
||||||
|
|
||||||
// TODO: apply RBAC
|
|
||||||
const showApproveButton = showEditButton;
|
const showApproveButton = showEditButton;
|
||||||
const showRejectButton = showEditButton;
|
const showRejectButton = showEditButton;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.detail'>
|
||||||
href={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`}
|
|
||||||
variant='ghost'
|
|
||||||
color='primary'
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{showEditButton && (
|
|
||||||
<Button
|
<Button
|
||||||
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${props.row.original.id}`}
|
href={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='warning'
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
Edit
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{showEditButton && (
|
||||||
|
<RequirePermission permissions='lti.production.transfer_to_laying.update'>
|
||||||
|
<Button
|
||||||
|
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='warning'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* TODO: apply RBAC */}
|
{/* TODO: apply RBAC */}
|
||||||
{showApproveButton && (
|
{showApproveButton && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
variant='ghost'
|
<Button
|
||||||
color='success'
|
variant='ghost'
|
||||||
onClick={approveClickHandler}
|
color='success'
|
||||||
className='justify-start text-sm'
|
onClick={approveClickHandler}
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
>
|
||||||
Approve
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
</Button>
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
{showRejectButton && (
|
{showRejectButton && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
variant='ghost'
|
<Button
|
||||||
color='error'
|
variant='ghost'
|
||||||
onClick={rejectClickHandler}
|
color='error'
|
||||||
className='justify-start text-sm'
|
onClick={rejectClickHandler}
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
>
|
||||||
Reject
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
</Button>
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
{showDeleteButton && (
|
{showDeleteButton && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.delete'>
|
||||||
onClick={deleteClickHandler}
|
<Button
|
||||||
variant='ghost'
|
onClick={deleteClickHandler}
|
||||||
color='error'
|
variant='ghost'
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
color='error'
|
||||||
>
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<Icon
|
||||||
width={16}
|
icon='material-symbols:delete-outline-rounded'
|
||||||
height={16}
|
width={16}
|
||||||
className='justify-start text-sm'
|
height={16}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Delete
|
/>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
@@ -502,47 +512,53 @@ const TransferToLayingsTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
||||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.create'>
|
||||||
href='/production/transfer-to-laying/add'
|
<Button
|
||||||
variant='outline'
|
href='/production/transfer-to-laying/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
{selectedRowIds.length > 0 && (
|
{selectedRowIds.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='success'
|
variant='outline'
|
||||||
onClick={bulkApproveClickHandler}
|
color='success'
|
||||||
disabled={selectedRowIds.length === 0}
|
onClick={bulkApproveClickHandler}
|
||||||
className='w-full sm:w-fit'
|
disabled={selectedRowIds.length === 0}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:check'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:check'
|
||||||
height={24}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
Approve
|
/>
|
||||||
</Button>
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='error'
|
variant='outline'
|
||||||
onClick={bulkRejectClickHandler}
|
color='error'
|
||||||
disabled={selectedRowIds.length === 0}
|
onClick={bulkRejectClickHandler}
|
||||||
className='w-full sm:w-fit'
|
disabled={selectedRowIds.length === 0}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:close'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:close'
|
||||||
height={24}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
Reject
|
/>
|
||||||
</Button>
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import useSWR from 'swr';
|
|||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import SelectInput, {
|
import SelectInput, {
|
||||||
OptionType,
|
OptionType,
|
||||||
useSelect,
|
useSelect,
|
||||||
@@ -500,34 +501,37 @@ const TransferToLayingForm = ({
|
|||||||
<>
|
<>
|
||||||
{isShowApproveRejectButton && (
|
{isShowApproveRejectButton && (
|
||||||
<div className='w-full flex flex-row justify-end gap-2'>
|
<div className='w-full flex flex-row justify-end gap-2'>
|
||||||
{/* TODO: apply RBAC */}
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='success'
|
color='success'
|
||||||
onClick={approveClickHandler}
|
onClick={approveClickHandler}
|
||||||
className='w-full sm:w-fit'
|
className='w-full sm:w-fit'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:check'
|
icon='material-symbols:check'
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
/>
|
/>
|
||||||
Approve
|
Approve
|
||||||
</Button>
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
variant='outline'
|
<Button
|
||||||
color='error'
|
variant='outline'
|
||||||
onClick={rejectClickHandler}
|
color='error'
|
||||||
className='w-full sm:w-fit'
|
onClick={rejectClickHandler}
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:close'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:close'
|
||||||
height={24}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
Reject
|
/>
|
||||||
</Button>
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -788,37 +792,41 @@ const TransferToLayingForm = ({
|
|||||||
{type !== 'add' && (
|
{type !== 'add' && (
|
||||||
<div className='flex flex-row justify-start gap-2'>
|
<div className='flex flex-row justify-start gap-2'>
|
||||||
{isShowDeleteButton && (
|
{isShowDeleteButton && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.delete'>
|
||||||
type='button'
|
<Button
|
||||||
color='error'
|
type='button'
|
||||||
onClick={deleteTransferToLayingClickHandler}
|
color='error'
|
||||||
className='px-4'
|
onClick={deleteTransferToLayingClickHandler}
|
||||||
>
|
className='px-4'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:delete-outline-rounded'
|
||||||
height={24}
|
width={24}
|
||||||
className='justify-start text-sm'
|
height={24}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Delete
|
/>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{type !== 'edit' && isShowEditButton && (
|
{type !== 'edit' && isShowEditButton && (
|
||||||
<Button
|
<RequirePermission permissions='lti.production.transfer_to_laying.update'>
|
||||||
type='button'
|
<Button
|
||||||
color='warning'
|
type='button'
|
||||||
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${initialValues?.id}`}
|
color='warning'
|
||||||
className='px-4'
|
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${initialValues?.id}`}
|
||||||
>
|
className='px-4'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:edit-outline'
|
<Icon
|
||||||
width={24}
|
icon='material-symbols:edit-outline'
|
||||||
height={24}
|
width={24}
|
||||||
className='justify-start text-sm'
|
height={24}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Edit
|
/>
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -833,15 +841,23 @@ const TransferToLayingForm = ({
|
|||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<RequirePermission
|
||||||
type='submit'
|
permissions={
|
||||||
color='primary'
|
type === 'add'
|
||||||
isLoading={formik.isSubmitting}
|
? 'lti.production.transfer_to_laying.create'
|
||||||
disabled={!formik.isValid || formik.isSubmitting}
|
: 'lti.production.transfer_to_laying.update'
|
||||||
className='px-4'
|
}
|
||||||
>
|
>
|
||||||
Submit
|
<Button
|
||||||
</Button>
|
type='submit'
|
||||||
|
color='primary'
|
||||||
|
isLoading={formik.isSubmitting}
|
||||||
|
disabled={!formik.isValid || formik.isSubmitting}
|
||||||
|
className='px-4'
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
@@ -38,15 +39,17 @@ const RowOptionsMenu = ({
|
|||||||
}: RowOptionsMenuProps) => {
|
}: RowOptionsMenuProps) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<RequirePermission permissions='lti.purchase.detail'>
|
||||||
href={`/purchase/detail/?purchaseId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/purchase/detail/?purchaseId=${props.row.original.id}`}
|
||||||
color='primary'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='primary'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
>
|
||||||
Detail
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
</Button>
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
{/*<Button*/}
|
{/*<Button*/}
|
||||||
{/* href={`/purchase/detail/edit/?purchaseId=${props.row.original.id}`}*/}
|
{/* href={`/purchase/detail/edit/?purchaseId=${props.row.original.id}`}*/}
|
||||||
@@ -58,20 +61,22 @@ const RowOptionsMenu = ({
|
|||||||
{/* Edit*/}
|
{/* Edit*/}
|
||||||
{/*</Button>*/}
|
{/*</Button>*/}
|
||||||
|
|
||||||
<Button
|
<RequirePermission permissions='lti.purchase.delete'>
|
||||||
onClick={deleteClickHandler}
|
<Button
|
||||||
variant='ghost'
|
onClick={deleteClickHandler}
|
||||||
color='error'
|
variant='ghost'
|
||||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
color='error'
|
||||||
>
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
<Icon
|
>
|
||||||
icon='material-symbols:delete-outline-rounded'
|
<Icon
|
||||||
width={16}
|
icon='material-symbols:delete-outline-rounded'
|
||||||
height={16}
|
width={16}
|
||||||
className='justify-start text-sm'
|
height={16}
|
||||||
/>
|
className='justify-start text-sm'
|
||||||
Delete
|
/>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -227,15 +232,17 @@ const PurchaseTable = () => {
|
|||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
||||||
<div className='w-full flex flex-row gap-2'>
|
<div className='w-full flex flex-row gap-2'>
|
||||||
<Button
|
<RequirePermission permissions='lti.purchase.create'>
|
||||||
href='/purchase/add'
|
<Button
|
||||||
variant='outline'
|
href='/purchase/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
} from '@/types/api/purchase/purchase';
|
} from '@/types/api/purchase/purchase';
|
||||||
import { BaseApproval, BaseGroupedApproval } from '@/types/api/api-general';
|
import { BaseApproval, BaseGroupedApproval } from '@/types/api/api-general';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
interface PurchaseOrderStaffApprovalFormProps {
|
interface PurchaseOrderStaffApprovalFormProps {
|
||||||
type?: 'add' | 'edit';
|
type?: 'add' | 'edit';
|
||||||
@@ -897,20 +898,25 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
{canUpdatePurchaseItems &&
|
{canUpdatePurchaseItems &&
|
||||||
canShowDeleteAddButtons && (
|
canShowDeleteAddButtons && (
|
||||||
<Button
|
<RequirePermission permissions='lti.purchase.delete.item'>
|
||||||
type='button'
|
<Button
|
||||||
color='error'
|
type='button'
|
||||||
onClick={() =>
|
color='error'
|
||||||
removePurchaseItem(formItemIndex)
|
className='text-sm w-fit'
|
||||||
}
|
onClick={() =>
|
||||||
title='Hapus item'
|
removePurchaseItem(
|
||||||
>
|
formItemIndex
|
||||||
<Icon
|
)
|
||||||
icon='mdi:trash-can'
|
}
|
||||||
width={16}
|
title='Hapus item'
|
||||||
height={16}
|
>
|
||||||
/>
|
<Icon
|
||||||
</Button>
|
icon='mdi:trash-can'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -39,19 +39,22 @@ import { toast } from 'react-hot-toast';
|
|||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
|
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
|
||||||
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
|
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
|
||||||
const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => {
|
const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type='dropdown'>
|
<RowOptionsMenuWrapper type='dropdown'>
|
||||||
<Button
|
<RequirePermission permissions='lti.purchase.update'>
|
||||||
onClick={onEdit}
|
<Button
|
||||||
variant='ghost'
|
onClick={onEdit}
|
||||||
color='warning'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='warning'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
>
|
||||||
Edit
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -59,15 +62,17 @@ const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => {
|
|||||||
const PenerimaanBarangDropdown = ({ onEdit }: { onEdit: () => void }) => {
|
const PenerimaanBarangDropdown = ({ onEdit }: { onEdit: () => void }) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type='dropdown'>
|
<RowOptionsMenuWrapper type='dropdown'>
|
||||||
<Button
|
<RequirePermission permissions='lti.purchase.receive'>
|
||||||
onClick={onEdit}
|
<Button
|
||||||
variant='ghost'
|
onClick={onEdit}
|
||||||
color='warning'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='warning'
|
||||||
>
|
className='justify-start text-sm'
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
>
|
||||||
Edit
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -496,14 +501,16 @@ const PurchaseOrderDetail = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<RequirePermission permissions='lti.purchase.delete.item'>
|
||||||
type='button'
|
<Button
|
||||||
color='error'
|
type='button'
|
||||||
className='text-sm'
|
color='error'
|
||||||
onClick={deleteClickHandler}
|
className='text-sm'
|
||||||
>
|
onClick={deleteClickHandler}
|
||||||
<Icon icon='mdi:trash-can' width={16} height={16} />
|
>
|
||||||
</Button>
|
<Icon icon='mdi:trash-can' width={16} height={16} />
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -632,25 +639,45 @@ const PurchaseOrderDetail = ({
|
|||||||
|
|
||||||
{showApprovalButton && (
|
{showApprovalButton && (
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
<Button
|
<RequirePermission
|
||||||
onClick={handleApprovalClick}
|
permissions={
|
||||||
variant='outline'
|
approvalStep === 1
|
||||||
color='success'
|
? 'lti.purchase.approve.staff'
|
||||||
className='w-full sm:w-fit'
|
: approvalStep === 2
|
||||||
|
? 'lti.purchase.approve.manager'
|
||||||
|
: 'lti.purchase.receive'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
<Button
|
||||||
Approve
|
onClick={handleApprovalClick}
|
||||||
</Button>
|
variant='outline'
|
||||||
|
color='success'
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
<Button
|
<RequirePermission
|
||||||
variant='outline'
|
permissions={
|
||||||
color='error'
|
approvalStep === 1
|
||||||
className='w-full sm:w-fit'
|
? 'lti.purchase.approve.staff'
|
||||||
onClick={handleRejectionClick}
|
: approvalStep === 2
|
||||||
|
? 'lti.purchase.approve.manager'
|
||||||
|
: 'lti.purchase.receive'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
<Button
|
||||||
Reject
|
variant='outline'
|
||||||
</Button>
|
color='error'
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
onClick={handleRejectionClick}
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import SelectInput from '@/components/input/SelectInput';
|
import SelectInput from '@/components/input/SelectInput';
|
||||||
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
export interface OptionType {
|
export interface OptionType {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -10,6 +11,7 @@ interface TableRowSizeSelectorProps {
|
|||||||
onChange: (val: OptionType | OptionType[] | null) => void;
|
onChange: (val: OptionType | OptionType[] | null) => void;
|
||||||
options: OptionType[];
|
options: OptionType[];
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TableRowSizeSelector = ({
|
export const TableRowSizeSelector = ({
|
||||||
@@ -17,9 +19,10 @@ export const TableRowSizeSelector = ({
|
|||||||
onChange,
|
onChange,
|
||||||
options,
|
options,
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
}: TableRowSizeSelectorProps) => {
|
}: TableRowSizeSelectorProps) => {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-row gap-3 items-end justify-end'>
|
<div className={cn('flex flex-row gap-3 items-end justify-end', className)}>
|
||||||
{children}
|
{children}
|
||||||
<SelectInput
|
<SelectInput
|
||||||
label='Baris'
|
label='Baris'
|
||||||
|
|||||||
+79
-1
@@ -10,14 +10,20 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
|||||||
text: 'Produksi',
|
text: 'Produksi',
|
||||||
link: '/production',
|
link: '/production',
|
||||||
icon: 'heroicons-outline:wrench-screwdriver',
|
icon: 'heroicons-outline:wrench-screwdriver',
|
||||||
|
permission: [
|
||||||
|
'lti.production.project_flocks.list',
|
||||||
|
'lti.production.recording.list',
|
||||||
|
],
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'Daftar Flock',
|
text: 'Daftar Flock',
|
||||||
link: '/production/project-flock',
|
link: '/production/project-flock',
|
||||||
|
permission: ['lti.production.project_flocks.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Recording',
|
text: 'Recording',
|
||||||
link: '/production/recording',
|
link: '/production/recording',
|
||||||
|
permission: ['lti.production.recording.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Transfer to Laying',
|
text: 'Transfer to Laying',
|
||||||
@@ -29,6 +35,7 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
|||||||
text: 'Pembelian',
|
text: 'Pembelian',
|
||||||
link: '/purchase',
|
link: '/purchase',
|
||||||
icon: 'heroicons-outline:shopping-cart',
|
icon: 'heroicons-outline:shopping-cart',
|
||||||
|
permission: ['lti.purchase.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Penjualan',
|
text: 'Penjualan',
|
||||||
@@ -36,14 +43,21 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
|||||||
icon: 'heroicons-outline:currency-dollar',
|
icon: 'heroicons-outline:currency-dollar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Biaya Operasional',
|
text: 'Keuangan',
|
||||||
|
link: '/finance',
|
||||||
|
icon: 'heroicons-outline:banknotes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Biaya',
|
||||||
link: '/expense',
|
link: '/expense',
|
||||||
icon: 'heroicons:wallet',
|
icon: 'heroicons:wallet',
|
||||||
|
permission: ['lti.expense.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Closing',
|
text: 'Closing',
|
||||||
link: '/closing',
|
link: '/closing',
|
||||||
icon: 'heroicons-outline:presentation-chart-bar',
|
icon: 'heroicons-outline:presentation-chart-bar',
|
||||||
|
permission: ['lti.closing.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Laporan',
|
text: 'Laporan',
|
||||||
@@ -68,18 +82,26 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
|||||||
text: 'Persediaan',
|
text: 'Persediaan',
|
||||||
link: '/inventory',
|
link: '/inventory',
|
||||||
icon: 'heroicons-outline:folder',
|
icon: 'heroicons-outline:folder',
|
||||||
|
permission: [
|
||||||
|
'lti.inventory.product_stock.list',
|
||||||
|
'lti.inventory.product_warehouses.list',
|
||||||
|
'lti.inventory.transfer.list',
|
||||||
|
],
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'Stok Produk',
|
text: 'Stok Produk',
|
||||||
link: '/inventory/product',
|
link: '/inventory/product',
|
||||||
|
permission: ['lti.inventory.product_stock.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Penyesuaian Stok',
|
text: 'Penyesuaian Stok',
|
||||||
link: '/inventory/adjustment',
|
link: '/inventory/adjustment',
|
||||||
|
permission: ['lti.inventory.product_stock.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Transfer Stok',
|
text: 'Transfer Stok',
|
||||||
link: '/inventory/movement',
|
link: '/inventory/movement',
|
||||||
|
permission: ['lti.inventory.transfer.list'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -87,58 +109,90 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
|||||||
text: 'Master Data',
|
text: 'Master Data',
|
||||||
link: '/master-data',
|
link: '/master-data',
|
||||||
icon: 'heroicons-outline:circle-stack',
|
icon: 'heroicons-outline:circle-stack',
|
||||||
|
permission: [
|
||||||
|
'lti.master.area.list',
|
||||||
|
'lti.master.banks.list',
|
||||||
|
'lti.master.customer.list',
|
||||||
|
'lti.master.fcr.list',
|
||||||
|
'lti.master.flocks.list',
|
||||||
|
'lti.master.kandangs.list',
|
||||||
|
'lti.master.locations.list',
|
||||||
|
'lti.master.nonstocks.list',
|
||||||
|
'lti.master.product_categories.list',
|
||||||
|
'lti.master.products.list',
|
||||||
|
'lti.master.suppliers.list',
|
||||||
|
'lti.master.uoms.list',
|
||||||
|
'lti.master.warehouses.list',
|
||||||
|
],
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'Produk',
|
text: 'Produk',
|
||||||
link: '/master-data/product',
|
link: '/master-data/product',
|
||||||
|
permission: ['lti.master.products.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Kategori Produk',
|
text: 'Kategori Produk',
|
||||||
link: '/master-data/product-category',
|
link: '/master-data/product-category',
|
||||||
|
permission: ['lti.master.product_categories.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Bank',
|
text: 'Bank',
|
||||||
link: '/master-data/bank',
|
link: '/master-data/bank',
|
||||||
|
permission: ['lti.master.banks.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Area',
|
text: 'Area',
|
||||||
link: '/master-data/area',
|
link: '/master-data/area',
|
||||||
|
permission: ['lti.master.area.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Lokasi',
|
text: 'Lokasi',
|
||||||
link: '/master-data/location',
|
link: '/master-data/location',
|
||||||
|
permission: ['lti.master.locations.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Kandang',
|
text: 'Kandang',
|
||||||
link: '/master-data/kandang',
|
link: '/master-data/kandang',
|
||||||
|
permission: ['lti.master.kandangs.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Warehouse',
|
text: 'Warehouse',
|
||||||
link: '/master-data/warehouse',
|
link: '/master-data/warehouse',
|
||||||
|
permission: ['lti.master.warehouses.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Customer',
|
text: 'Customer',
|
||||||
link: '/master-data/customer',
|
link: '/master-data/customer',
|
||||||
|
permission: ['lti.master.customer.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'UOM',
|
text: 'UOM',
|
||||||
link: '/master-data/uom',
|
link: '/master-data/uom',
|
||||||
|
permission: ['lti.master.uoms.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Non-Stock',
|
text: 'Non-Stock',
|
||||||
link: '/master-data/nonstock',
|
link: '/master-data/nonstock',
|
||||||
|
permission: ['lti.master.nonstocks.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'FCR',
|
text: 'FCR',
|
||||||
link: '/master-data/fcr',
|
link: '/master-data/fcr',
|
||||||
|
permission: ['lti.master.fcr.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Supplier',
|
text: 'Supplier',
|
||||||
link: '/master-data/supplier',
|
link: '/master-data/supplier',
|
||||||
|
permission: ['lti.master.suppliers.list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Flock',
|
text: 'Flock',
|
||||||
link: '/master-data/flock',
|
link: '/master-data/flock',
|
||||||
|
permission: ['lti.master.flocks.list'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Standar Produksi',
|
||||||
|
link: '/master-data/production-standard',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -233,6 +287,30 @@ export const RECORDING_FLAG_OPTIONS = [
|
|||||||
{ label: 'Ayam Mati', value: 'Ayam Mati' },
|
{ label: 'Ayam Mati', value: 'Ayam Mati' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const FINANCE_PARTY_TYPE_OPTIONS = [
|
||||||
|
{ label: 'Customer', value: 'CUSTOMER' },
|
||||||
|
{ label: 'Supplier', value: 'SUPPLIER' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FINANCE_PAYMENT_METHOD_OPTIONS = [
|
||||||
|
{ label: 'Transfer', value: 'TRANSFER' },
|
||||||
|
{ label: 'Cash', value: 'CASH' },
|
||||||
|
{ label: 'Card', value: 'CARD' },
|
||||||
|
{ label: 'Cheque', value: 'CHEQUE' },
|
||||||
|
{ label: 'Saldo', value: 'SALDO' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FINANCE_INITIAL_BALANCE_TYPE_OPTIONS = [
|
||||||
|
{ label: 'Saldo Awal Positif', value: 'POSITIVE' },
|
||||||
|
{ label: 'Saldo Awal Negatif', value: 'NEGATIVE' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FINANCE_TRANSACTION_STATUS = ['PENJUALAN', 'BIAYA'];
|
||||||
|
|
||||||
|
export const FINANCE_INITIAL_BALANCE_STATUS = ['SALDO_AWAL'];
|
||||||
|
|
||||||
|
export const FINANCE_INJECTION_STATUS = ['INJECTION'];
|
||||||
|
|
||||||
export const APPROVAL_WORKFLOWS = [
|
export const APPROVAL_WORKFLOWS = [
|
||||||
{
|
{
|
||||||
key: 'PROJECT_FLOCKS',
|
key: 'PROJECT_FLOCKS',
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
export const ROUTE_PERMISSIONS: Record<string, string[]> = {
|
||||||
|
'/': ['lti.dashboard.list'],
|
||||||
|
|
||||||
|
// Dashboard
|
||||||
|
'/dashboard/': ['lti.dashboard.list'],
|
||||||
|
|
||||||
|
// Production
|
||||||
|
// Production - Project Flock
|
||||||
|
'/production/project-flock/': ['lti.production.project_flocks.list'],
|
||||||
|
'/production/project-flock/add/': [
|
||||||
|
'lti.production.project_flocks.create',
|
||||||
|
'lti.production.project_flocks.delete',
|
||||||
|
],
|
||||||
|
'/production/project-flock/detail/': ['lti.production.project_flocks.detail'],
|
||||||
|
'/production/project-flock/detail/edit/': [
|
||||||
|
'lti.production.project_flocks.update',
|
||||||
|
'lti.production.project_flocks.delete',
|
||||||
|
],
|
||||||
|
'/production/project-flock/chickin/add/kandang/': [
|
||||||
|
'lti.production.chickins.create',
|
||||||
|
'lti.production.chickins.detail',
|
||||||
|
'lti.production.chickins.approve',
|
||||||
|
],
|
||||||
|
'/production/project-flock/closing/': [
|
||||||
|
'lti.production.project_flock_kandangs.closing',
|
||||||
|
'lti.production.project_flock_kandangs.closing.detail',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Production - Recording
|
||||||
|
'/production/recording/': ['lti.production.recording.list'],
|
||||||
|
'/production/recording/add/': ['lti.production.recording.create'],
|
||||||
|
'/production/recording/detail/': ['lti.production.recording.detail'],
|
||||||
|
'/production/recording/detail/edit/': ['lti.production.recording.update'],
|
||||||
|
|
||||||
|
// Production - Transfer to Laying
|
||||||
|
'/production/transfer-to-laying/': ['lti.production.transfer_to_laying.list'],
|
||||||
|
'/production/transfer-to-laying/add/': [
|
||||||
|
'lti.production.transfer_to_laying.create',
|
||||||
|
],
|
||||||
|
'/production/transfer-to-laying/detail/': [
|
||||||
|
'lti.production.transfer_to_laying.detail',
|
||||||
|
],
|
||||||
|
'/production/transfer-to-laying/detail/edit/': [
|
||||||
|
'lti.production.transfer_to_laying.update',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Purchase
|
||||||
|
'/purchase/': ['lti.purchase.list'],
|
||||||
|
'/purchase/add/': ['lti.purchase.create'],
|
||||||
|
'/purchase/detail/': ['lti.purchase.detail'],
|
||||||
|
'/purchase/detail/edit/': ['lti.purchase.update'],
|
||||||
|
|
||||||
|
// Marketing
|
||||||
|
'/marketing/': ['lti.marketing.delivery_order.list'],
|
||||||
|
'/marketing/add/delivery-orders/': ['lti.marketing.delivery_order.create'],
|
||||||
|
'/marketing/add/sales-orders/': ['lti.marketing.sales_order.create'],
|
||||||
|
'/marketing/detail/': ['lti.marketing.delivery_order.detail'],
|
||||||
|
'/marketing/detail/delivery-orders/edit/': [
|
||||||
|
'lti.marketing.delivery_order.update',
|
||||||
|
],
|
||||||
|
'/marketing/detail/sales-orders/edit/': ['lti.marketing.sales_order.update'],
|
||||||
|
|
||||||
|
// Expense
|
||||||
|
'/expense/': ['lti.expense.list'],
|
||||||
|
'/expense/add/': ['lti.expense.create'],
|
||||||
|
'/expense/detail/': ['lti.expense.detail'],
|
||||||
|
'/expense/detail/edit/': ['lti.expense.update'],
|
||||||
|
'/expense/realization/': ['lti.expense.create.realization'],
|
||||||
|
'/expense/realization/edit/': ['lti.expense.update.realization'],
|
||||||
|
|
||||||
|
// Finance
|
||||||
|
// // ===== FINANCE =====
|
||||||
|
// "lti.finance.transaction.list",
|
||||||
|
// "lti.finance.transaction.detail",
|
||||||
|
// "lti.finance.transaction.delete",
|
||||||
|
// "lti.finance.payments.create",
|
||||||
|
// "lti.finance.payments.update",
|
||||||
|
// "lti.finance.initial_balances.create",
|
||||||
|
// "lti.finance.initial_balances.update",
|
||||||
|
// "lti.finance.injections.create",
|
||||||
|
// "lti.finance.injections.update",
|
||||||
|
'/finance/': ['lti.finance.transaction.list'],
|
||||||
|
'/finance/detail/': ['lti.finance.transaction.detail'],
|
||||||
|
'/finance/add/': ['lti.finance.payments.create'],
|
||||||
|
'/finance/detail/edit/': ['lti.finance.payments.update'],
|
||||||
|
'/finance/add/initial-balance/': ['lti.finance.initial_balances.create'],
|
||||||
|
'/finance/detail/edit/initial-balance/': [
|
||||||
|
'lti.finance.initial_balances.update',
|
||||||
|
],
|
||||||
|
'/finance/add/injection/': ['lti.finance.injections.create'],
|
||||||
|
'/finance/detail/edit/injection/': ['lti.finance.injections.update'],
|
||||||
|
|
||||||
|
// Closing
|
||||||
|
'/closing/': ['lti.closing.list'],
|
||||||
|
'/closing/detail/': ['lti.closing.detail'],
|
||||||
|
|
||||||
|
// Report
|
||||||
|
'/report/logistic-stock/': ['lti.repport.purchasesupplier.list'],
|
||||||
|
'/report/expense/': ['lti.repport.expense.list'],
|
||||||
|
'/report/marketing/': ['lti.repport.delivery.list'],
|
||||||
|
|
||||||
|
// Inventory
|
||||||
|
'/inventory/adjustment/': ['lti.inventory.list'],
|
||||||
|
'/inventory/adjustment/add/': ['lti.inventory.create'],
|
||||||
|
'/inventory/adjustment/detail/': ['lti.inventory.detail'],
|
||||||
|
'/inventory/movement/': ['lti.inventory.transfer.list'],
|
||||||
|
'/inventory/movement/add/': ['lti.inventory.transfer.create'],
|
||||||
|
'/inventory/movement/detail/': ['lti.inventory.transfer.detail'],
|
||||||
|
'/inventory/movement/detail/edit/': ['lti.inventory.transfer.update'],
|
||||||
|
'/inventory/product/': ['lti.inventory.product_stock.list'],
|
||||||
|
'/inventory/product/detail/': ['lti.inventory.product_stock.detail'],
|
||||||
|
|
||||||
|
// Master Data
|
||||||
|
'/master-data/product/': ['lti.master.products.list'],
|
||||||
|
'/master-data/product/add/': ['lti.master.products.create'],
|
||||||
|
'/master-data/product/detail/': ['lti.master.products.detail'],
|
||||||
|
'/master-data/product/detail/edit/': ['lti.master.products.update'],
|
||||||
|
|
||||||
|
'/master-data/product-category/': ['lti.master.product_categories.list'],
|
||||||
|
'/master-data/product-category/add/': [
|
||||||
|
'lti.master.product_categories.create',
|
||||||
|
],
|
||||||
|
'/master-data/product-category/detail/': [
|
||||||
|
'lti.master.product_categories.detail',
|
||||||
|
],
|
||||||
|
'/master-data/product-category/detail/edit/': [
|
||||||
|
'lti.master.product_categories.update',
|
||||||
|
],
|
||||||
|
|
||||||
|
'/master-data/bank/': ['lti.master.banks.list'],
|
||||||
|
'/master-data/bank/add/': ['lti.master.banks.create'],
|
||||||
|
'/master-data/bank/detail/': ['lti.master.banks.detail'],
|
||||||
|
'/master-data/bank/detail/edit/': ['lti.master.banks.update'],
|
||||||
|
|
||||||
|
'/master-data/area/': ['lti.master.area.list'],
|
||||||
|
'/master-data/area/add/': ['lti.master.area.create'],
|
||||||
|
'/master-data/area/detail/': ['lti.master.area.detail'],
|
||||||
|
'/master-data/area/detail/edit/': ['lti.master.area.update'],
|
||||||
|
|
||||||
|
'/master-data/location/': ['lti.master.locations.list'],
|
||||||
|
'/master-data/location/add/': ['lti.master.locations.create'],
|
||||||
|
'/master-data/location/detail/': ['lti.master.locations.detail'],
|
||||||
|
'/master-data/location/detail/edit/': ['lti.master.locations.update'],
|
||||||
|
|
||||||
|
'/master-data/kandang/': ['lti.master.kandangs.list'],
|
||||||
|
'/master-data/kandang/add/': ['lti.master.kandangs.create'],
|
||||||
|
'/master-data/kandang/detail/': ['lti.master.kandangs.detail'],
|
||||||
|
'/master-data/kandang/detail/edit/': ['lti.master.kandangs.update'],
|
||||||
|
|
||||||
|
'/master-data/warehouse/': ['lti.master.warehouses.list'],
|
||||||
|
'/master-data/warehouse/add/': ['lti.master.warehouses.create'],
|
||||||
|
'/master-data/warehouse/detail/': ['lti.master.warehouses.detail'],
|
||||||
|
'/master-data/warehouse/detail/edit/': ['lti.master.warehouses.update'],
|
||||||
|
|
||||||
|
'/master-data/customer/': ['lti.master.customer.list'],
|
||||||
|
'/master-data/customer/add/': ['lti.master.customer.create'],
|
||||||
|
'/master-data/customer/detail/': ['lti.master.customer.detail'],
|
||||||
|
'/master-data/customer/detail/edit/': ['lti.master.customer.update'],
|
||||||
|
|
||||||
|
'/master-data/uom/': ['lti.master.uoms.list'],
|
||||||
|
'/master-data/uom/add/': ['lti.master.uoms.create'],
|
||||||
|
'/master-data/uom/detail/': ['lti.master.uoms.detail'],
|
||||||
|
'/master-data/uom/detail/edit/': ['lti.master.uoms.update'],
|
||||||
|
|
||||||
|
'/master-data/nonstock/': ['lti.master.nonstocks.list'],
|
||||||
|
'/master-data/nonstock/add/': ['lti.master.nonstocks.create'],
|
||||||
|
'/master-data/nonstock/detail/': ['lti.master.nonstocks.detail'],
|
||||||
|
'/master-data/nonstock/detail/edit/': ['lti.master.nonstocks.update'],
|
||||||
|
|
||||||
|
'/master-data/fcr/': ['lti.master.fcr.list'],
|
||||||
|
'/master-data/fcr/add/': ['lti.master.fcr.create'],
|
||||||
|
'/master-data/fcr/detail/': ['lti.master.fcr.detail'],
|
||||||
|
'/master-data/fcr/detail/edit/': ['lti.master.fcr.update'],
|
||||||
|
|
||||||
|
'/master-data/supplier/': ['lti.master.suppliers.list'],
|
||||||
|
'/master-data/supplier/add/': ['lti.master.suppliers.create'],
|
||||||
|
'/master-data/supplier/detail/': ['lti.master.suppliers.detail'],
|
||||||
|
'/master-data/supplier/detail/edit/': ['lti.master.suppliers.update'],
|
||||||
|
|
||||||
|
'/master-data/flock/': ['lti.master.flocks.list'],
|
||||||
|
'/master-data/flock/add/': ['lti.master.flocks.create'],
|
||||||
|
'/master-data/flock/detail/': ['lti.master.flocks.detail'],
|
||||||
|
'/master-data/flock/detail/edit/': ['lti.master.flocks.update'],
|
||||||
|
|
||||||
|
'/master-data/production-standard/': ['lti.master.production_standards.list'],
|
||||||
|
'/master-data/production-standard/add/': [
|
||||||
|
'lti.master.production_standards.create',
|
||||||
|
],
|
||||||
|
'/master-data/production-standard/detail/': [
|
||||||
|
'lti.master.production_standards.detail',
|
||||||
|
],
|
||||||
|
'/master-data/production-standard/detail/edit/': [
|
||||||
|
'lti.master.production_standards.update',
|
||||||
|
],
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,139 +0,0 @@
|
|||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
|
||||||
import { DailyMarketingReport } from '@/types/api/report/marketing';
|
|
||||||
|
|
||||||
// TODO: delete this later
|
|
||||||
export const DAILY_MARKETING_DUMMY_DATA: BaseApiResponse<DailyMarketingReport> =
|
|
||||||
{
|
|
||||||
code: 200,
|
|
||||||
status: 'success',
|
|
||||||
message: 'Get daily marketing report successfully',
|
|
||||||
meta: {
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
total_pages: 1,
|
|
||||||
total_results: 2,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
// metadata
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 101,
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin User',
|
|
||||||
},
|
|
||||||
created_at: '2025-12-01T08:00:00Z',
|
|
||||||
updated_at: '2025-12-01T08:00:00Z',
|
|
||||||
|
|
||||||
// row data
|
|
||||||
no: 1,
|
|
||||||
so_date: '2025-12-01',
|
|
||||||
do_date: '2025-12-08',
|
|
||||||
aging_days: 7,
|
|
||||||
|
|
||||||
warehouse: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Warehouse Kandang A',
|
|
||||||
type: 'KANDANG',
|
|
||||||
area: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Area Barat',
|
|
||||||
},
|
|
||||||
location: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Farm Bandung',
|
|
||||||
address: 'Jl. Raya Farm No. 1',
|
|
||||||
area: null,
|
|
||||||
},
|
|
||||||
kandang: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Kandang A1',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
capacity: 5000,
|
|
||||||
location: null,
|
|
||||||
pic: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
customer: {
|
|
||||||
id: 1,
|
|
||||||
name: 'PT Maju Jaya',
|
|
||||||
pic_id: 10,
|
|
||||||
pic: {
|
|
||||||
id: 10,
|
|
||||||
id_user: 210,
|
|
||||||
email: 'pic@majujaya.com',
|
|
||||||
name: 'Budi Santoso',
|
|
||||||
},
|
|
||||||
type: 'BROILER',
|
|
||||||
address: 'Jl. Industri No. 10',
|
|
||||||
phone: '08123456789',
|
|
||||||
email: 'contact@majujaya.com',
|
|
||||||
account_number: '1234567890',
|
|
||||||
},
|
|
||||||
|
|
||||||
sales: 'Andi Wijaya',
|
|
||||||
|
|
||||||
product: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Live Chicken',
|
|
||||||
brand: 'LTI Farm',
|
|
||||||
sku: 'LC-001',
|
|
||||||
product_price: 18_000,
|
|
||||||
selling_price: 20_000,
|
|
||||||
tax: 0,
|
|
||||||
expiry_period: 0,
|
|
||||||
uom: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Kg',
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 101,
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin User',
|
|
||||||
},
|
|
||||||
created_at: '2025-01-01T00:00:00Z',
|
|
||||||
updated_at: '2025-01-01T00:00:00Z',
|
|
||||||
},
|
|
||||||
product_category: {
|
|
||||||
id: 1,
|
|
||||||
code: 'BROILER',
|
|
||||||
name: 'Broiler Chicken',
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 101,
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin User',
|
|
||||||
},
|
|
||||||
created_at: '2025-01-01T00:00:00Z',
|
|
||||||
updated_at: '2025-01-01T00:00:00Z',
|
|
||||||
},
|
|
||||||
suppliers: [],
|
|
||||||
flags: ['LIVE'],
|
|
||||||
},
|
|
||||||
|
|
||||||
do_number: 'DO-2025-0001',
|
|
||||||
vehicle_number: 'B 1234 CD',
|
|
||||||
marketing_type: 'REGULAR',
|
|
||||||
|
|
||||||
qty: 1000,
|
|
||||||
average_weight_kg: 1.8,
|
|
||||||
total_weight_kg: 1800,
|
|
||||||
|
|
||||||
sales_price_per_kg: 20_000,
|
|
||||||
hpp_price_per_kg: 18_000,
|
|
||||||
|
|
||||||
sales_amount: 36_000_000,
|
|
||||||
hpp_amount: 32_400_000,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
summary: {
|
|
||||||
total_qty: 1000,
|
|
||||||
total_weight_kg: 1800,
|
|
||||||
total_sales_amount: 36_000_000,
|
|
||||||
total_hpp_amount: 32_400_000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -15,20 +15,6 @@ import {
|
|||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
||||||
import { ClosingSales } from '@/types/api/closing';
|
import { ClosingSales } from '@/types/api/closing';
|
||||||
|
|
||||||
// TODO: delete these dummy data later
|
|
||||||
import {
|
|
||||||
dummyGetAllFetcher,
|
|
||||||
dummyGetSingle,
|
|
||||||
dummyGetAllIncomingSapronakFetcher,
|
|
||||||
dummyGetAllOutgoingSapronakFetcher,
|
|
||||||
dummyGetGeneralInfo,
|
|
||||||
dummyGetPerhitunganSapronak,
|
|
||||||
dummyGetOverhead,
|
|
||||||
dummyClosingProductionData,
|
|
||||||
} from '@/dummy/closing.dummy';
|
|
||||||
import { sleep } from '@/lib/helper';
|
|
||||||
|
|
||||||
export class ClosingApiService extends BaseApiService<Closing, null, null> {
|
export class ClosingApiService extends BaseApiService<Closing, null, null> {
|
||||||
constructor(basePath: string) {
|
constructor(basePath: string) {
|
||||||
super(basePath);
|
super(basePath);
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { BaseApiService } from '@/services/api/base';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
||||||
|
import {
|
||||||
|
CreateFinancePayment,
|
||||||
|
CreateInitialBalance,
|
||||||
|
CreateInjection,
|
||||||
|
Finance,
|
||||||
|
UpdateFinancePayment,
|
||||||
|
UpdateInitialBalance,
|
||||||
|
UpdateInjection,
|
||||||
|
} from '@/types/api/finance/finance';
|
||||||
|
|
||||||
|
export class FinanceApiService extends BaseApiService<
|
||||||
|
Finance,
|
||||||
|
unknown,
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
|
constructor(basePath: string) {
|
||||||
|
super(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSingle(id: number): Promise<BaseApiResponse<Finance>> {
|
||||||
|
return await httpClientFetcher<BaseApiResponse<Finance>>(
|
||||||
|
`${this.basePath}/transactions/${id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(payload: CreateFinancePayment) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
|
try {
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
|
const createRes = await httpClient<BaseApiResponse<Finance>>(
|
||||||
|
`${this.basePath}/payments`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: payload,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return createRes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<Finance>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInitialBalances(payload: CreateInitialBalance) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
|
try {
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
|
const createRes = await httpClient<BaseApiResponse<Finance>>(
|
||||||
|
`${this.basePath}/initial-balances`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: payload,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return createRes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<Finance>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInjections(payload: CreateInjection) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
|
try {
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
|
const createRes = await httpClient<BaseApiResponse<Finance>>(
|
||||||
|
`${this.basePath}/injections`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: payload,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return createRes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<Finance>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, payload: UpdateFinancePayment) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
|
try {
|
||||||
|
const updatePath = `${this.basePath}/payments/${id}`;
|
||||||
|
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
|
const updateRes = await httpClient<BaseApiResponse<Finance>>(updatePath, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: payload,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
return updateRes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<Finance>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateInitialBalances(id: number, payload: UpdateInitialBalance) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
|
try {
|
||||||
|
const updatePath = `${this.basePath}/initial-balances/${id}`;
|
||||||
|
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
|
const updateRes = await httpClient<BaseApiResponse<Finance>>(updatePath, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: payload,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
return updateRes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<Finance>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateInjections(id: number, payload: UpdateInjection) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
|
try {
|
||||||
|
const updatePath = `${this.basePath}/injections/${id}`;
|
||||||
|
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
|
const updateRes = await httpClient<BaseApiResponse<Finance>>(updatePath, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: payload,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
return updateRes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<Finance>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: number) {
|
||||||
|
try {
|
||||||
|
const deletePath = `${this.basePath}/transactions/${id}`;
|
||||||
|
const deleteRes = await httpClient<BaseApiResponse>(deletePath, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
return deleteRes;
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FinanceApi = new FinanceApiService('/finance');
|
||||||
@@ -64,6 +64,7 @@ import {
|
|||||||
Flock,
|
Flock,
|
||||||
UpdateFlockPayload,
|
UpdateFlockPayload,
|
||||||
} from '@/types/api/master-data/flock';
|
} from '@/types/api/master-data/flock';
|
||||||
|
import { ProductionStandard } from '@/types/api/master-data/production-standard';
|
||||||
|
|
||||||
export const UomApi = new BaseApiService<
|
export const UomApi = new BaseApiService<
|
||||||
Uom,
|
Uom,
|
||||||
@@ -141,3 +142,9 @@ export const FlockApi = new BaseApiService<
|
|||||||
CreateFlockPayload,
|
CreateFlockPayload,
|
||||||
UpdateFlockPayload
|
UpdateFlockPayload
|
||||||
>('/master-data/flocks');
|
>('/master-data/flocks');
|
||||||
|
|
||||||
|
export const ProductionStandardApi = new BaseApiService<
|
||||||
|
ProductionStandard,
|
||||||
|
unknown,
|
||||||
|
unknown
|
||||||
|
>('/master-data/production-standards');
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
import { FormStore } from '@/types/stores';
|
||||||
|
import { createProductionStandardFormSlice } from '@/stores/form/slices/production-standard-form.slice';
|
||||||
|
|
||||||
|
export const useFormStore = create<FormStore>()(
|
||||||
|
devtools(
|
||||||
|
persist(
|
||||||
|
(...args) => ({
|
||||||
|
...createProductionStandardFormSlice(...args),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'production-standard-form-cache',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{
|
||||||
|
name: 'FormStore',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { StateCreator } from 'zustand';
|
||||||
|
import { FormStore } from '@/types/stores';
|
||||||
|
|
||||||
|
export const createProductionStandardFormSlice: StateCreator<
|
||||||
|
FormStore,
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
FormStore
|
||||||
|
> = (set): FormStore => ({
|
||||||
|
formData: null,
|
||||||
|
editMode: false,
|
||||||
|
editIndex: null,
|
||||||
|
|
||||||
|
setFormData: (data) =>
|
||||||
|
set(() => ({
|
||||||
|
formData: data,
|
||||||
|
})),
|
||||||
|
|
||||||
|
setEditMode: (mode, index) =>
|
||||||
|
set(() => ({
|
||||||
|
editMode: mode,
|
||||||
|
editIndex: index,
|
||||||
|
})),
|
||||||
|
|
||||||
|
addDetail: (detail) =>
|
||||||
|
set((state) => ({
|
||||||
|
formData: state.formData
|
||||||
|
? {
|
||||||
|
...state.formData,
|
||||||
|
details: [...state.formData.details, detail],
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateDetail: (index, detail) =>
|
||||||
|
set((state) => {
|
||||||
|
if (!state.formData) return state;
|
||||||
|
const newDetails = [...state.formData.details];
|
||||||
|
newDetails[index] = detail;
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
...state.formData,
|
||||||
|
details: newDetails,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteDetail: (index) =>
|
||||||
|
set((state) => {
|
||||||
|
if (!state.formData) return state;
|
||||||
|
const newDetails = [...state.formData.details];
|
||||||
|
newDetails.splice(index, 1);
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
...state.formData,
|
||||||
|
details: newDetails,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
clearCache: () =>
|
||||||
|
set(() => ({
|
||||||
|
formData: null,
|
||||||
|
editMode: false,
|
||||||
|
editIndex: null,
|
||||||
|
})),
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user