mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
chore: prettier format
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
|
npm run format
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run build
|
npm run build
|
||||||
|
|||||||
Generated
+44
@@ -16,6 +16,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "15.5.3",
|
"next": "15.5.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-number-format": "^5.4.4",
|
"react-number-format": "^5.4.4",
|
||||||
@@ -194,6 +195,12 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@date-fns/tz": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz",
|
||||||
@@ -2868,6 +2875,22 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date-fns-jalali": {
|
||||||
|
"version": "4.1.0-0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
|
||||||
|
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
@@ -5741,6 +5764,27 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-day-picker": {
|
||||||
|
"version": "9.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.1.tgz",
|
||||||
|
"integrity": "sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@date-fns/tz": "^1.4.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"date-fns-jalali": "^4.1.0-0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/gpbl"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "15.5.3",
|
"next": "15.5.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-number-format": "^5.4.4",
|
"react-number-format": "^5.4.4",
|
||||||
|
|||||||
@@ -8,91 +8,6 @@ import TransferToLayingForm from '@/components/pages/production/transfer-to-layi
|
|||||||
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
|
||||||
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
|
|
||||||
|
|
||||||
// TODO: delete dummy data
|
|
||||||
const DUMMY_TRANSFER_TO_LAYING_EDIT: TransferToLaying = {
|
|
||||||
id: 1,
|
|
||||||
transfer_date: '2025-10-14',
|
|
||||||
flock_source: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Flock asal test',
|
|
||||||
},
|
|
||||||
flock_destination: {
|
|
||||||
id: 2,
|
|
||||||
name: 'Flock tujuan destination',
|
|
||||||
},
|
|
||||||
quantity: 10,
|
|
||||||
kandangs: [
|
|
||||||
{
|
|
||||||
kandang: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Kandang test',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
location: {
|
|
||||||
id: 1,
|
|
||||||
name: 'test location',
|
|
||||||
address: 'test address 1',
|
|
||||||
area: { id: 1, name: 'test area 1' },
|
|
||||||
},
|
|
||||||
pic: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_at: '14-10-2025',
|
|
||||||
updated_at: '14-10-2025',
|
|
||||||
},
|
|
||||||
quantity: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kandang: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Kandang test 2',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
location: {
|
|
||||||
id: 1,
|
|
||||||
name: 'test location',
|
|
||||||
address: 'test address 1',
|
|
||||||
area: { id: 1, name: 'test area 1' },
|
|
||||||
},
|
|
||||||
pic: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_at: '14-10-2025',
|
|
||||||
updated_at: '14-10-2025',
|
|
||||||
},
|
|
||||||
quantity: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
reason: 'Test alasan',
|
|
||||||
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_at: '14-10-2025',
|
|
||||||
updated_at: '14-10-2025',
|
|
||||||
};
|
|
||||||
|
|
||||||
const TransferToLayingEdit = () => {
|
const TransferToLayingEdit = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -114,33 +29,33 @@ const TransferToLayingEdit = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove dummy data and integrate with real API
|
|
||||||
if (
|
if (
|
||||||
!isLoadingTransferToLaying &&
|
!isLoadingTransferToLaying &&
|
||||||
(!transferToLaying ||
|
(!transferToLaying || isResponseError(transferToLaying))
|
||||||
(isResponseError(transferToLaying) && !DUMMY_TRANSFER_TO_LAYING_EDIT))
|
|
||||||
) {
|
) {
|
||||||
router.replace('/404');
|
router.replace('/404');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isResponseSuccess(transferToLaying) &&
|
||||||
|
transferToLaying.data.approval.step_number === 2
|
||||||
|
) {
|
||||||
|
router.replace('/production/transfer-to-laying');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full p-4 flex flex-row justify-center'>
|
<div className='w-full p-4 flex flex-row justify-center'>
|
||||||
{isLoadingTransferToLaying && (
|
{isLoadingTransferToLaying && (
|
||||||
<span className='loading loading-spinner loading-xl' />
|
<span className='loading loading-spinner loading-xl' />
|
||||||
)}
|
)}
|
||||||
{/* {!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
|
{!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
|
||||||
<TransferToLayingForm
|
<TransferToLayingForm
|
||||||
type='detail'
|
type='edit'
|
||||||
initialValues={transferToLaying.data}
|
initialValues={transferToLaying.data}
|
||||||
/>
|
/>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
{/* TODO: remove this dummy data and integrate to real API */}
|
|
||||||
<TransferToLayingForm
|
|
||||||
type='edit'
|
|
||||||
initialValues={DUMMY_TRANSFER_TO_LAYING_EDIT}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,91 +8,6 @@ import TransferToLayingForm from '@/components/pages/production/transfer-to-layi
|
|||||||
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
|
||||||
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
|
|
||||||
|
|
||||||
// TODO: delete dummy data
|
|
||||||
const DUMMY_TRANSFER_TO_LAYING_DETAIL: TransferToLaying = {
|
|
||||||
id: 1,
|
|
||||||
transfer_date: '2025-10-14',
|
|
||||||
flock_source: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Flock asal test',
|
|
||||||
},
|
|
||||||
flock_destination: {
|
|
||||||
id: 2,
|
|
||||||
name: 'Flock tujuan destination',
|
|
||||||
},
|
|
||||||
quantity: 10,
|
|
||||||
kandangs: [
|
|
||||||
{
|
|
||||||
kandang: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Kandang test',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
location: {
|
|
||||||
id: 1,
|
|
||||||
name: 'test location',
|
|
||||||
address: 'test address 1',
|
|
||||||
area: { id: 1, name: 'test area 1' },
|
|
||||||
},
|
|
||||||
pic: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_at: '14-10-2025',
|
|
||||||
updated_at: '14-10-2025',
|
|
||||||
},
|
|
||||||
quantity: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kandang: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Kandang test 2',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
location: {
|
|
||||||
id: 1,
|
|
||||||
name: 'test location',
|
|
||||||
address: 'test address 1',
|
|
||||||
area: { id: 1, name: 'test area 1' },
|
|
||||||
},
|
|
||||||
pic: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_at: '14-10-2025',
|
|
||||||
updated_at: '14-10-2025',
|
|
||||||
},
|
|
||||||
quantity: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
reason: 'Test alasan',
|
|
||||||
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 2,
|
|
||||||
email: 'test@gmail.com',
|
|
||||||
name: 'test',
|
|
||||||
},
|
|
||||||
created_at: '14-10-2025',
|
|
||||||
updated_at: '14-10-2025',
|
|
||||||
};
|
|
||||||
|
|
||||||
const TransferToLayingDetail = () => {
|
const TransferToLayingDetail = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -114,11 +29,9 @@ const TransferToLayingDetail = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove dummy data and integrate with real API
|
|
||||||
if (
|
if (
|
||||||
!isLoadingTransferToLaying &&
|
!isLoadingTransferToLaying &&
|
||||||
(!transferToLaying ||
|
(!transferToLaying || isResponseError(transferToLaying))
|
||||||
(isResponseError(transferToLaying) && !DUMMY_TRANSFER_TO_LAYING_DETAIL))
|
|
||||||
) {
|
) {
|
||||||
router.replace('/404');
|
router.replace('/404');
|
||||||
return;
|
return;
|
||||||
@@ -129,18 +42,13 @@ const TransferToLayingDetail = () => {
|
|||||||
{isLoadingTransferToLaying && (
|
{isLoadingTransferToLaying && (
|
||||||
<span className='loading loading-spinner loading-xl' />
|
<span className='loading loading-spinner loading-xl' />
|
||||||
)}
|
)}
|
||||||
{/* {!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
|
|
||||||
|
{!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
|
||||||
<TransferToLayingForm
|
<TransferToLayingForm
|
||||||
type='detail'
|
type='detail'
|
||||||
initialValues={transferToLaying.data}
|
initialValues={transferToLaying.data}
|
||||||
/>
|
/>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
{/* TODO: remove this dummy data and integrate to real API */}
|
|
||||||
<TransferToLayingForm
|
|
||||||
type='detail'
|
|
||||||
initialValues={DUMMY_TRANSFER_TO_LAYING_DETAIL}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+37
-23
@@ -1,6 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ReactNode, RefObject, useCallback, useRef, useState } from 'react';
|
import {
|
||||||
|
ReactNode,
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
export const useModal = () => {
|
export const useModal = () => {
|
||||||
@@ -8,31 +15,34 @@ export const useModal = () => {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const openModal = useCallback(() => {
|
const openModal = useCallback(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
ref.current.show();
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
|
||||||
ref.current?.showModal();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = useCallback(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
ref.current.close();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
ref.current?.close();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggle = useCallback(() => {
|
const toggle = useCallback(() => {
|
||||||
if (open) {
|
open ? closeModal() : openModal();
|
||||||
closeModal();
|
|
||||||
} else {
|
|
||||||
openModal();
|
|
||||||
}
|
|
||||||
}, [open, closeModal, openModal]);
|
}, [open, closeModal, openModal]);
|
||||||
|
|
||||||
if (ref.current) {
|
useEffect(() => {
|
||||||
ref.current.addEventListener('close', () => {
|
const dialog = ref.current;
|
||||||
closeModal();
|
if (!dialog) return;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ref, open, setOpen, openModal, closeModal, toggle } as const;
|
const handleClose = () => setOpen(false);
|
||||||
|
dialog.addEventListener('close', handleClose);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dialog.removeEventListener('close', handleClose);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { ref, open, openModal, closeModal, toggle } as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
@@ -46,15 +56,19 @@ interface ModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Modal = ({ ref, children, closeOnBackdrop, className }: ModalProps) => {
|
const Modal = ({ ref, children, closeOnBackdrop, className }: ModalProps) => {
|
||||||
return (
|
const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
|
||||||
<dialog ref={ref} className={cn('modal', className?.modal)}>
|
if (closeOnBackdrop && e.target === ref.current) {
|
||||||
<div className={cn('modal-box', className?.modalBox)}>{children}</div>
|
ref.current?.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
{closeOnBackdrop && (
|
return (
|
||||||
<form method='dialog' className='modal-backdrop'>
|
<dialog
|
||||||
<button>close</button>
|
ref={ref}
|
||||||
</form>
|
className={cn('modal', className?.modal)}
|
||||||
)}
|
onClick={handleBackdropClick}
|
||||||
|
>
|
||||||
|
<div className={cn('modal-box', className?.modalBox)}>{children}</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
FilterFn,
|
FilterFn,
|
||||||
SortingState,
|
SortingState,
|
||||||
OnChangeFn,
|
OnChangeFn,
|
||||||
|
Row,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { rankItem } from '@tanstack/match-sorter-utils';
|
import { rankItem } from '@tanstack/match-sorter-utils';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -50,6 +51,7 @@ export interface TableProps<TData extends object> {
|
|||||||
manualSorting?: boolean;
|
manualSorting?: boolean;
|
||||||
rowSelection?: Record<string, boolean>;
|
rowSelection?: Record<string, boolean>;
|
||||||
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
||||||
|
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||||
@@ -90,6 +92,7 @@ const Table = <TData extends object>({
|
|||||||
manualSorting = false,
|
manualSorting = false,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
setRowSelection,
|
setRowSelection,
|
||||||
|
enableRowSelection,
|
||||||
}: TableProps<TData>) => {
|
}: TableProps<TData>) => {
|
||||||
const isServerSideTable =
|
const isServerSideTable =
|
||||||
totalItems !== undefined &&
|
totalItems !== undefined &&
|
||||||
@@ -150,6 +153,10 @@ const Table = <TData extends object>({
|
|||||||
tableOptions.getRowId = (row) => (row as { id: string }).id;
|
tableOptions.getRowId = (row) => (row as { id: string }).id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enableRowSelection !== undefined) {
|
||||||
|
tableOptions.enableRowSelection = enableRowSelection;
|
||||||
|
}
|
||||||
|
|
||||||
const table = useReactTable(tableOptions);
|
const table = useReactTable(tableOptions);
|
||||||
const { setPageSize } = table;
|
const { setPageSize } = table;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ChangeEventHandler, FocusEventHandler, ReactNode } from 'react';
|
import {
|
||||||
|
ChangeEventHandler,
|
||||||
import { cn } from '@/lib/helper';
|
FocusEventHandler,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
|
import { DateRange, DayPicker, Matcher } from 'react-day-picker';
|
||||||
|
import 'react-day-picker/dist/style.css';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
export interface DateInputProps {
|
export interface DateInputProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
bottomLabel?: string;
|
bottomLabel?: string;
|
||||||
name: string;
|
name: string;
|
||||||
value?: string;
|
value?: string | { from?: string; to?: string };
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
min?: string;
|
min?: string;
|
||||||
max?: string;
|
max?: string;
|
||||||
@@ -24,9 +33,8 @@ export interface DateInputProps {
|
|||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
isRange?: boolean;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
startAdornment?: ReactNode;
|
|
||||||
endAdornment?: ReactNode;
|
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||||
}
|
}
|
||||||
@@ -36,22 +44,144 @@ const DateInput = ({
|
|||||||
bottomLabel,
|
bottomLabel,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder = 'dd/mm/yyyy',
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
className,
|
className,
|
||||||
isError,
|
isError: externalError,
|
||||||
isValid,
|
isValid: externalValid,
|
||||||
errorMessage,
|
errorMessage: externalErrorMessage,
|
||||||
startAdornment,
|
|
||||||
endAdornment,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
required = false,
|
required = false,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
isRange = false,
|
||||||
}: DateInputProps) => {
|
}: DateInputProps) => {
|
||||||
|
const [internalError, setInternalError] = useState<string | null>(null);
|
||||||
|
const [selected, setSelected] = useState<Date | undefined>();
|
||||||
|
const [selectedRange, setSelectedRange] = useState<{
|
||||||
|
from?: Date;
|
||||||
|
to?: Date;
|
||||||
|
}>({});
|
||||||
|
const [displayValue, setDisplayValue] = useState<string>('');
|
||||||
|
|
||||||
|
const minDate = min
|
||||||
|
? new Date(min.split('/').reverse().join('-'))
|
||||||
|
: undefined;
|
||||||
|
const maxDate = max
|
||||||
|
? new Date(max.split('/').reverse().join('-'))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const calendarModal = useModal();
|
||||||
|
|
||||||
|
// --- Sync value props ---
|
||||||
|
useEffect(() => {
|
||||||
|
if (!value) return;
|
||||||
|
if (isRange && typeof value === 'object') {
|
||||||
|
const from = value.from ? new Date(value.from) : undefined;
|
||||||
|
const to = value.to ? new Date(value.to) : undefined;
|
||||||
|
setSelectedRange({ from, to });
|
||||||
|
setDisplayValue(
|
||||||
|
`${from ? formatDate(from, 'DD/MM/YYYY') : ''} ${
|
||||||
|
to ? '- ' + formatDate(to, 'DD/MM/YYYY') : ''
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
const iso = value.includes('/')
|
||||||
|
? value.split('/').reverse().join('-')
|
||||||
|
: value;
|
||||||
|
const date = new Date(iso);
|
||||||
|
setSelected(date);
|
||||||
|
setDisplayValue(formatDate(iso, 'DD/MM/YYYY'));
|
||||||
|
}
|
||||||
|
}, [value, isRange]);
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!disabled && !readOnly) calendarModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur: FocusEventHandler<HTMLInputElement> = (e) => {
|
||||||
|
onBlur?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectSingle = (selectedDate?: Date) => {
|
||||||
|
if (!selectedDate) return;
|
||||||
|
if (minDate && selectedDate < minDate) {
|
||||||
|
setInternalError(`Tanggal tidak boleh sebelum ${min}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (maxDate && selectedDate > maxDate) {
|
||||||
|
setInternalError(`Tanggal tidak boleh setelah ${max}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInternalError(null);
|
||||||
|
setSelected(selectedDate);
|
||||||
|
const formattedDisplay = formatDate(selectedDate, 'DD/MM/YYYY');
|
||||||
|
const formattedISO = formatDate(selectedDate, 'YYYY-MM-DD');
|
||||||
|
setDisplayValue(formattedDisplay);
|
||||||
|
|
||||||
|
const syntheticEvent = {
|
||||||
|
target: { name, value: formattedISO },
|
||||||
|
} as unknown as React.ChangeEvent<HTMLInputElement>;
|
||||||
|
onChange?.(syntheticEvent);
|
||||||
|
calendarModal.closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectRange = (range?: { from?: Date; to?: Date }) => {
|
||||||
|
if (!range) return;
|
||||||
|
setSelectedRange(range);
|
||||||
|
|
||||||
|
const fromStr = range.from ? formatDate(range.from, 'DD/MM/YYYY') : '';
|
||||||
|
const toStr = range.to ? formatDate(range.to, 'DD/MM/YYYY') : '';
|
||||||
|
setDisplayValue(`${fromStr}${toStr ? ' - ' + toStr : ''}`);
|
||||||
|
|
||||||
|
// Jika kedua tanggal sudah terpilih
|
||||||
|
if (range.from && range.to) {
|
||||||
|
if (minDate && range.from < minDate) {
|
||||||
|
setInternalError(`Tanggal mulai tidak boleh sebelum ${min}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (maxDate && range.to > maxDate) {
|
||||||
|
setInternalError(`Tanggal akhir tidak boleh setelah ${max}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setInternalError(null);
|
||||||
|
const syntheticEvent = {
|
||||||
|
target: {
|
||||||
|
name,
|
||||||
|
value: {
|
||||||
|
from: formatDate(range.from, 'YYYY-MM-DD'),
|
||||||
|
to: formatDate(range.to, 'YYYY-MM-DD'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as React.ChangeEvent<HTMLInputElement>;
|
||||||
|
onChange?.(syntheticEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetDate = () => {
|
||||||
|
setSelected(undefined);
|
||||||
|
setSelectedRange({});
|
||||||
|
setDisplayValue('');
|
||||||
|
const syntheticEvent = {
|
||||||
|
target: { name, value: isRange ? { from: '', to: '' } : '' },
|
||||||
|
} as unknown as React.ChangeEvent<HTMLInputElement>;
|
||||||
|
onChange?.(syntheticEvent);
|
||||||
|
calendarModal.closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveDate = () => {
|
||||||
|
if (internalError) return;
|
||||||
|
calendarModal.closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalIsError = externalError || !!internalError;
|
||||||
|
const finalErrorMessage = internalError || externalErrorMessage;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -64,65 +194,135 @@ const DateInput = ({
|
|||||||
htmlFor={name}
|
htmlFor={name}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full text-sm font-normal leading-5',
|
'w-full text-sm font-normal leading-5',
|
||||||
{
|
{ 'text-error': finalIsError },
|
||||||
'text-error': isError,
|
|
||||||
},
|
|
||||||
className?.label
|
className?.label
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{required && (
|
{required && (
|
||||||
<>
|
<span className='text-error' title='required'>
|
||||||
{' '}
|
*
|
||||||
<span className='tooltip tooltip-error' data-tip='required'>
|
</span>
|
||||||
<span className='text-error'>*</span>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all duration-200 flex items-center',
|
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded transition-all duration-200 flex items-center border',
|
||||||
{
|
{
|
||||||
'border-error': isError,
|
'border-error': finalIsError,
|
||||||
'border-success!': isValid,
|
'border-success': externalValid && !finalIsError,
|
||||||
},
|
},
|
||||||
className?.inputWrapper
|
className?.inputWrapper
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{startAdornment && startAdornment}
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type='date'
|
type='text'
|
||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
placeholder={placeholder}
|
placeholder={isRange ? 'dd/mm/yyyy - dd/mm/yyyy' : placeholder}
|
||||||
value={value}
|
value={displayValue}
|
||||||
onChange={onChange}
|
onBlur={handleBlur}
|
||||||
onBlur={onBlur}
|
onClick={handleClick}
|
||||||
min={min}
|
|
||||||
max={max}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn('grow bg-transparent cursor-pointer', className?.input)}
|
readOnly // ✅ tidak bisa diketik manual
|
||||||
readOnly={readOnly}
|
className={cn(
|
||||||
|
'grow bg-transparent cursor-pointer focus:outline-none',
|
||||||
|
className?.input
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(isLoading || endAdornment) && (
|
{isLoading && (
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
{isLoading && <span className='loading loading-spinner' />}
|
<span className='loading loading-spinner' />
|
||||||
{endAdornment && endAdornment}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Icon
|
||||||
|
icon='uil:calendar'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='cursor-pointer text-dark'
|
||||||
|
onClick={(e) =>
|
||||||
|
handleClick(e as unknown as React.MouseEvent<HTMLInputElement>)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isError && bottomLabel && (
|
{!finalIsError && bottomLabel && (
|
||||||
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
)}
|
)}
|
||||||
{isError && errorMessage && (
|
{finalIsError && finalErrorMessage && (
|
||||||
<p className='w-full text-sm text-error'>{errorMessage}</p>
|
<p className='w-full text-sm text-error'>{finalErrorMessage}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
ref={calendarModal.ref}
|
||||||
|
className={{
|
||||||
|
modal: 'rounded',
|
||||||
|
modalBox: `w-fit min-h-${isRange ? '124' : '110'} flex flex-col`,
|
||||||
|
}}
|
||||||
|
closeOnBackdrop
|
||||||
|
>
|
||||||
|
{isRange ? (
|
||||||
|
<DayPicker
|
||||||
|
required={required}
|
||||||
|
mode='range'
|
||||||
|
captionLayout='dropdown-years'
|
||||||
|
navLayout='around'
|
||||||
|
reverseYears
|
||||||
|
defaultMonth={selectedRange.from ?? new Date()}
|
||||||
|
startMonth={minDate ?? new Date(1999, 1)}
|
||||||
|
endMonth={maxDate ?? new Date(new Date().getFullYear() + 5, 11)}
|
||||||
|
selected={selectedRange as DateRange}
|
||||||
|
onSelect={handleSelectRange}
|
||||||
|
footer={<div className='text-center mt-3'>{displayValue}</div>}
|
||||||
|
disabled={
|
||||||
|
[
|
||||||
|
minDate ? { before: minDate } : undefined,
|
||||||
|
maxDate ? { after: maxDate } : undefined,
|
||||||
|
].filter(Boolean) as Matcher[]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DayPicker
|
||||||
|
required={required}
|
||||||
|
mode='single'
|
||||||
|
captionLayout='dropdown-years'
|
||||||
|
navLayout='around'
|
||||||
|
reverseYears
|
||||||
|
defaultMonth={selected ?? new Date()}
|
||||||
|
startMonth={minDate ?? new Date(1999, 1)}
|
||||||
|
endMonth={maxDate ?? new Date(new Date().getFullYear() + 5, 11)}
|
||||||
|
selected={selected}
|
||||||
|
onSelect={handleSelectSingle}
|
||||||
|
disabled={
|
||||||
|
[
|
||||||
|
minDate ? { before: minDate } : undefined,
|
||||||
|
maxDate ? { after: maxDate } : undefined,
|
||||||
|
].filter(Boolean) as Matcher[]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className='mt-auto flex flex-col gap-2'>
|
||||||
|
{isRange && (
|
||||||
|
<small className='text-secondary'>
|
||||||
|
Tekan dua kali untuk memilih tanggal awal
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='flex h-full justify-end items-end gap-2'>
|
||||||
|
<Button type='button' color='warning' onClick={handleResetDate}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
{isRange && (
|
||||||
|
<Button type='button' onClick={handleSaveDate}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Button from '@/components/Button';
|
|||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
|
|
||||||
interface ConfirmationModalProps {
|
export interface ConfirmationModalProps {
|
||||||
ref: RefObject<HTMLDialogElement | null>;
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
type?: 'info' | 'success' | 'error';
|
type?: 'info' | 'success' | 'error';
|
||||||
text?: string;
|
text?: string;
|
||||||
@@ -92,11 +92,7 @@ const ConfirmationModal = ({
|
|||||||
{text ?? 'Apakah anda yakin ingin melakukan hal ini?'}
|
{text ?? 'Apakah anda yakin ingin melakukan hal ini?'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{children && (
|
{children && <div className='w-full'>{children}</div>}
|
||||||
<div className='w-full'>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='w-full flex flex-row gap-2'>
|
<div className='w-full flex flex-row gap-2'>
|
||||||
{secondaryButton && secondaryButton.text && (
|
{secondaryButton && secondaryButton.text && (
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ChangeEventHandler, useId, useState } from 'react';
|
||||||
|
|
||||||
|
import ConfirmationModal, {
|
||||||
|
ConfirmationModalProps,
|
||||||
|
} from '@/components/modal/ConfirmationModal';
|
||||||
|
import TextArea from '@/components/input/TextArea';
|
||||||
|
|
||||||
|
import { Color } from '@/types/theme';
|
||||||
|
|
||||||
|
interface ConfirmationModalWithNotesProps
|
||||||
|
extends Omit<ConfirmationModalProps, 'children' | 'primaryButton'> {
|
||||||
|
rows?: number;
|
||||||
|
placeholder?: string;
|
||||||
|
|
||||||
|
primaryButton?: {
|
||||||
|
text?: string;
|
||||||
|
color?: Color;
|
||||||
|
isLoading?: boolean;
|
||||||
|
onClick?: (notes: string) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmationModalWithNotes: React.FC<ConfirmationModalWithNotesProps> = ({
|
||||||
|
ref,
|
||||||
|
type = 'info',
|
||||||
|
text,
|
||||||
|
closeOnBackdrop,
|
||||||
|
primaryButton,
|
||||||
|
secondaryButton,
|
||||||
|
className,
|
||||||
|
rows = 3,
|
||||||
|
placeholder = 'Catatan...',
|
||||||
|
}) => {
|
||||||
|
const randomId = useId();
|
||||||
|
const [notes, setNotes] = useState('');
|
||||||
|
|
||||||
|
const notesChangeHandler: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
||||||
|
setNotes(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmationModal
|
||||||
|
ref={ref}
|
||||||
|
type={type}
|
||||||
|
text={text}
|
||||||
|
closeOnBackdrop={closeOnBackdrop}
|
||||||
|
primaryButton={{
|
||||||
|
...primaryButton,
|
||||||
|
onClick: () => {
|
||||||
|
primaryButton?.onClick?.(notes);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
secondaryButton={secondaryButton}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
name={randomId}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={notes}
|
||||||
|
onChange={notesChangeHandler}
|
||||||
|
rows={rows}
|
||||||
|
/>
|
||||||
|
</ConfirmationModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmationModalWithNotes;
|
||||||
@@ -130,10 +130,17 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
|
|
||||||
if (!approvalGroup) {
|
if (!approvalGroup) {
|
||||||
const isWaiting = currentStepNumber === latestApproval.step_number + 1;
|
const isWaiting = currentStepNumber === latestApproval.step_number + 1;
|
||||||
|
const isPreviousApprovalRejected =
|
||||||
|
groupedApprovals[groupedApprovals.length - 1].approvals[0].action ===
|
||||||
|
'REJECTED';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: approvalLineItem.step_name,
|
name: approvalLineItem.step_name,
|
||||||
status: isWaiting ? 'WAITING' : 'IDLE',
|
status: isPreviousApprovalRejected
|
||||||
|
? 'IDLE'
|
||||||
|
: isWaiting
|
||||||
|
? 'WAITING'
|
||||||
|
: 'IDLE',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -625,12 +625,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
let options: OptionType[] = [];
|
let options: OptionType[] = [];
|
||||||
|
|
||||||
if (isResponseSuccess(locations)) {
|
if (isResponseSuccess(locations)) {
|
||||||
options = options.concat(
|
const locationOptionsList = locations?.data.map((location) => ({
|
||||||
locations?.data.map((location) => ({
|
value: location.id,
|
||||||
value: location.id,
|
label: location.name || '',
|
||||||
label: location.name,
|
})) || [];
|
||||||
})) || []
|
options = options.concat(locationOptionsList);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||||
@@ -641,7 +640,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
) {
|
) {
|
||||||
options.push({
|
options.push({
|
||||||
value: currentLocation.id,
|
value: currentLocation.id,
|
||||||
label: currentLocation.name,
|
label: currentLocation.name || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -653,12 +652,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
let options: OptionType[] = [];
|
let options: OptionType[] = [];
|
||||||
|
|
||||||
if (isResponseSuccess(projectFlocks)) {
|
if (isResponseSuccess(projectFlocks)) {
|
||||||
options = options.concat(
|
const flockOptions = projectFlocks?.data.map((projectFlock) => ({
|
||||||
projectFlocks?.data.map((projectFlock) => ({
|
value: projectFlock.id,
|
||||||
value: projectFlock.id,
|
label: projectFlock.flock_name || '',
|
||||||
label: projectFlock.flock_name,
|
})) || [];
|
||||||
})) || []
|
options = options.concat(flockOptions);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||||
@@ -669,7 +667,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
) {
|
) {
|
||||||
options.push({
|
options.push({
|
||||||
value: currentProjectFlock.id,
|
value: currentProjectFlock.id,
|
||||||
label: currentProjectFlock.flock_name,
|
label: currentProjectFlock.flock_name || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -686,12 +684,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (selectedProjectFlockData?.kandangs) {
|
if (selectedProjectFlockData?.kandangs) {
|
||||||
options = options.concat(
|
const kandangOptions = selectedProjectFlockData.kandangs.map((kandang: Kandang) => ({
|
||||||
selectedProjectFlockData.kandangs.map((kandang: Kandang) => ({
|
value: kandang.id,
|
||||||
value: kandang.id,
|
label: kandang.name || '',
|
||||||
label: kandang.name,
|
}));
|
||||||
}))
|
options = options.concat(kandangOptions);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -703,7 +700,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
) {
|
) {
|
||||||
options.push({
|
options.push({
|
||||||
value: currentKandang.id,
|
value: currentKandang.id,
|
||||||
label: currentKandang.name,
|
label: currentKandang.name || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -870,7 +867,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||||
baseValues.project_flock_kandang = {
|
baseValues.project_flock_kandang = {
|
||||||
value: projectFlockKandangDetail.project_flock.id,
|
value: projectFlockKandangDetail.project_flock.id,
|
||||||
label: projectFlockKandangDetail.project_flock.flock_name,
|
label: projectFlockKandangDetail.project_flock.flock_name || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1186,21 +1183,21 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
if (location) {
|
if (location) {
|
||||||
const locationOption = {
|
const locationOption = {
|
||||||
value: location.id,
|
value: location.id,
|
||||||
label: location.name,
|
label: location.name || '',
|
||||||
};
|
};
|
||||||
setSelectedLocation(locationOption);
|
setSelectedLocation(locationOption);
|
||||||
|
|
||||||
if (projectFlock) {
|
if (projectFlock) {
|
||||||
const projectFlockOption = {
|
const projectFlockOption = {
|
||||||
value: projectFlock.id,
|
value: projectFlock.id,
|
||||||
label: projectFlock.flock_name,
|
label: projectFlock.flock_name || '',
|
||||||
};
|
};
|
||||||
setSelectedProjectFlock(projectFlockOption);
|
setSelectedProjectFlock(projectFlockOption);
|
||||||
|
|
||||||
if (kandang) {
|
if (kandang) {
|
||||||
const kandangOption = {
|
const kandangOption = {
|
||||||
value: kandang.id,
|
value: kandang.id,
|
||||||
label: kandang.name,
|
label: kandang.name || '',
|
||||||
};
|
};
|
||||||
setSelectedKandang(kandangOption);
|
setSelectedKandang(kandangOption);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
import { ChangeEventHandler, useState } from 'react';
|
import { ChangeEventHandler, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
import {
|
||||||
|
CellContext,
|
||||||
|
ColumnDef,
|
||||||
|
Row,
|
||||||
|
SortingState,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -20,6 +25,7 @@ import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
|||||||
import TextInput from '@/components/input/TextInput';
|
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 { 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';
|
||||||
@@ -29,6 +35,7 @@ import { useTableFilter } from '@/services/hooks/useTableFilter';
|
|||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { Flock } from '@/types/api/master-data/flock';
|
import { Flock } from '@/types/api/master-data/flock';
|
||||||
import { FlockApi } from '@/services/api/master-data';
|
import { FlockApi } from '@/services/api/master-data';
|
||||||
|
import PillBadge from '@/components/PillBadge';
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
@@ -43,6 +50,16 @@ const RowOptionsMenu = ({
|
|||||||
rejectClickHandler: () => void;
|
rejectClickHandler: () => void;
|
||||||
deleteClickHandler: () => void;
|
deleteClickHandler: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const showEditButton =
|
||||||
|
props.row.original.approval.action !== 'APPROVED' &&
|
||||||
|
props.row.original.approval.action !== 'REJECTED';
|
||||||
|
|
||||||
|
const showDeleteButton = showEditButton;
|
||||||
|
|
||||||
|
// TODO: apply RBAC
|
||||||
|
const showApproveButton = showEditButton;
|
||||||
|
const showRejectButton = showEditButton;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<Button
|
||||||
@@ -55,50 +72,57 @@ const RowOptionsMenu = ({
|
|||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
{showEditButton && (
|
||||||
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${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} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant='ghost'
|
|
||||||
color='success'
|
|
||||||
onClick={approveClickHandler}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
|
||||||
Approve
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
onClick={rejectClickHandler}
|
|
||||||
className='justify-start text-sm'
|
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
|
||||||
Reject
|
|
||||||
</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='material-symbols:edit-outline' width={16} height={16} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* TODO: apply RBAC */}
|
||||||
|
{showApproveButton && (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='success'
|
||||||
|
onClick={approveClickHandler}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showRejectButton && (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
onClick={rejectClickHandler}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showDeleteButton && (
|
||||||
|
<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>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -187,17 +211,24 @@ const TransferToLayingsTable = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => {
|
||||||
<div>
|
const isCheckboxDisabled =
|
||||||
<CheckboxInput
|
!row.getCanSelect() ||
|
||||||
name='row'
|
row.original.approval.action === 'APPROVED' ||
|
||||||
checked={row.getIsSelected()}
|
row.original.approval.action === 'REJECTED';
|
||||||
disabled={!row.getCanSelect()}
|
|
||||||
indeterminate={row.getIsSomeSelected()}
|
return (
|
||||||
onChange={row.getToggleSelectedHandler()}
|
<div>
|
||||||
/>
|
<CheckboxInput
|
||||||
</div>
|
name='row'
|
||||||
),
|
checked={row.getIsSelected()}
|
||||||
|
disabled={isCheckboxDisabled}
|
||||||
|
indeterminate={row.getIsSomeSelected()}
|
||||||
|
onChange={row.getToggleSelectedHandler()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: '#',
|
header: '#',
|
||||||
@@ -214,21 +245,55 @@ const TransferToLayingsTable = () => {
|
|||||||
{
|
{
|
||||||
accessorKey: 'flock_source',
|
accessorKey: 'flock_source',
|
||||||
header: 'Flock Asal',
|
header: 'Flock Asal',
|
||||||
cell: (props) => props.row.original.flock_source.name,
|
cell: (props) => props.row.original.from_project_flock.flock_name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'flock_destination',
|
accessorKey: 'flock_destination',
|
||||||
header: 'Flock Tujuan',
|
header: 'Flock Tujuan',
|
||||||
cell: (props) => props.row.original.flock_destination.name,
|
cell: (props) => props.row.original.to_project_flock.flock_name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'quantity',
|
accessorKey: 'usage_qty',
|
||||||
header: 'Kuantitas',
|
header: 'Kuantitas',
|
||||||
|
cell: (props) => props.getValue() ?? props.row.original.pending_usage_qty,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'reason',
|
accessorKey: 'notes',
|
||||||
header: 'Alasan Transfer',
|
header: 'Alasan Transfer',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: 'Status',
|
||||||
|
cell: (props) => {
|
||||||
|
const isLatestApprovalRejected =
|
||||||
|
props.row.original.approval.action === 'REJECTED';
|
||||||
|
let latestApprovalStepName = props.row.original.approval.step_name;
|
||||||
|
|
||||||
|
let pillBadgeColor: 'yellow' | 'green' | 'gray' | 'red' = 'gray';
|
||||||
|
|
||||||
|
switch (latestApprovalStepName.toLowerCase()) {
|
||||||
|
case 'pengajuan':
|
||||||
|
pillBadgeColor = 'yellow';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'disetujui':
|
||||||
|
pillBadgeColor = 'green';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLatestApprovalRejected) {
|
||||||
|
pillBadgeColor = 'red';
|
||||||
|
latestApprovalStepName = 'Ditolak';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PillBadge
|
||||||
|
content={latestApprovalStepName}
|
||||||
|
color={pillBadgeColor}
|
||||||
|
className='text-sm'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: 'Aksi',
|
header: 'Aksi',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
@@ -237,7 +302,7 @@ const TransferToLayingsTable = () => {
|
|||||||
const currentRowRelativeIndex =
|
const currentRowRelativeIndex =
|
||||||
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||||
|
|
||||||
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 3;
|
||||||
|
|
||||||
const approveClickHandler = () => {
|
const approveClickHandler = () => {
|
||||||
setSelectedTransferToLaying(props.row.original);
|
setSelectedTransferToLaying(props.row.original);
|
||||||
@@ -268,7 +333,7 @@ const TransferToLayingsTable = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{currentPageSize > 2 && (
|
{currentPageSize > 3 && (
|
||||||
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||||
<RowOptionsMenu
|
<RowOptionsMenu
|
||||||
type='dropdown'
|
type='dropdown'
|
||||||
@@ -280,7 +345,7 @@ const TransferToLayingsTable = () => {
|
|||||||
</RowDropdownOptions>
|
</RowDropdownOptions>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentPageSize <= 2 && (
|
{currentPageSize <= 3 && (
|
||||||
<RowCollapseOptions>
|
<RowCollapseOptions>
|
||||||
<RowOptionsMenu
|
<RowOptionsMenu
|
||||||
type='collapse'
|
type='collapse'
|
||||||
@@ -297,6 +362,15 @@ const TransferToLayingsTable = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const tableEnableRowSelectionHandler: (
|
||||||
|
row: Row<TransferToLaying>
|
||||||
|
) => boolean = (row) => {
|
||||||
|
return (
|
||||||
|
row.original.approval.action !== 'APPROVED' &&
|
||||||
|
row.original.approval.action !== 'REJECTED'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const bulkApproveClickHandler = () => {
|
const bulkApproveClickHandler = () => {
|
||||||
approveModal.openModal();
|
approveModal.openModal();
|
||||||
};
|
};
|
||||||
@@ -309,27 +383,31 @@ const TransferToLayingsTable = () => {
|
|||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
await TransferToLayingApi.delete(selectedTransferToLaying?.id as number);
|
try {
|
||||||
refreshTransferToLayings();
|
await TransferToLayingApi.delete(selectedTransferToLaying?.id as number);
|
||||||
|
|
||||||
deleteModal.closeModal();
|
toast.success('Berhasil menghapus data transfer ke laying!');
|
||||||
toast.success('Berhasil menghapus data transfer ke laying!');
|
refreshTransferToLayings();
|
||||||
setIsDeleteLoading(false);
|
} catch (error) {
|
||||||
|
toast.success('Gagal menghapus data transfer ke laying!');
|
||||||
|
} finally {
|
||||||
|
deleteModal.closeModal();
|
||||||
|
setIsDeleteLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmationModalApproveClickHandler = async () => {
|
const confirmationModalApproveClickHandler = async (notes: string) => {
|
||||||
setIsApproveLoading(true);
|
setIsApproveLoading(true);
|
||||||
|
|
||||||
const bulkApproveResponse =
|
const bulkApproveResponse = await TransferToLayingApi.bulkApprove(
|
||||||
await TransferToLayingApi.bulkApprove(selectedRowIds);
|
selectedRowIds,
|
||||||
|
notes
|
||||||
|
);
|
||||||
|
|
||||||
if (isResponseSuccess(bulkApproveResponse)) {
|
if (isResponseSuccess(bulkApproveResponse)) {
|
||||||
refreshTransferToLayings();
|
refreshTransferToLayings();
|
||||||
approveModal.closeModal();
|
approveModal.closeModal();
|
||||||
|
|
||||||
// TODO: remove console.log
|
|
||||||
console.log('Approved data:', selectedRowIds);
|
|
||||||
|
|
||||||
toast.success(
|
toast.success(
|
||||||
`Berhasil approve ${selectedRowIds.length} data transfer ke laying!`
|
`Berhasil approve ${selectedRowIds.length} data transfer ke laying!`
|
||||||
);
|
);
|
||||||
@@ -346,19 +424,18 @@ const TransferToLayingsTable = () => {
|
|||||||
setIsApproveLoading(false);
|
setIsApproveLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmationModalRejectClickHandler = async () => {
|
const confirmationModalRejectClickHandler = async (notes: string) => {
|
||||||
setIsRejectLoading(true);
|
setIsRejectLoading(true);
|
||||||
|
|
||||||
const bulkRejectResponse =
|
const bulkRejectResponse = await TransferToLayingApi.bulkReject(
|
||||||
await TransferToLayingApi.bulkReject(selectedRowIds);
|
selectedRowIds,
|
||||||
|
notes
|
||||||
|
);
|
||||||
|
|
||||||
if (isResponseSuccess(bulkRejectResponse)) {
|
if (isResponseSuccess(bulkRejectResponse)) {
|
||||||
refreshTransferToLayings();
|
refreshTransferToLayings();
|
||||||
rejectModal.closeModal();
|
rejectModal.closeModal();
|
||||||
|
|
||||||
// TODO: remove console.log
|
|
||||||
console.log('Rejected data:', selectedRowIds);
|
|
||||||
|
|
||||||
toast.success(
|
toast.success(
|
||||||
`Berhasil reject ${selectedRowIds.length} data transfer ke laying!`
|
`Berhasil reject ${selectedRowIds.length} data transfer ke laying!`
|
||||||
);
|
);
|
||||||
@@ -559,6 +636,7 @@ const TransferToLayingsTable = () => {
|
|||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
setRowSelection={setRowSelection}
|
setRowSelection={setRowSelection}
|
||||||
|
enableRowSelection={tableEnableRowSelectionHandler}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn({
|
containerClassName: cn({
|
||||||
'mb-20':
|
'mb-20':
|
||||||
@@ -592,7 +670,7 @@ const TransferToLayingsTable = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModalWithNotes
|
||||||
ref={approveModal.ref}
|
ref={approveModal.ref}
|
||||||
type='success'
|
type='success'
|
||||||
text={`Apakah anda yakin ingin approve data transfer ke laying ini (${selectedRowIds.length} data)?`}
|
text={`Apakah anda yakin ingin approve data transfer ke laying ini (${selectedRowIds.length} data)?`}
|
||||||
@@ -607,7 +685,7 @@ const TransferToLayingsTable = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModalWithNotes
|
||||||
ref={rejectModal.ref}
|
ref={rejectModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
text={`Apakah anda yakin ingin reject data transfer ke laying ini (${selectedRowIds.length} data)?`}
|
text={`Apakah anda yakin ingin reject data transfer ke laying ini (${selectedRowIds.length} data)?`}
|
||||||
|
|||||||
+156
-2
@@ -1,4 +1,7 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
|
||||||
|
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
|
||||||
type TransferToLayingFormSchemaType = {
|
type TransferToLayingFormSchemaType = {
|
||||||
transfer_date?: string;
|
transfer_date?: string;
|
||||||
@@ -14,7 +17,7 @@ type TransferToLayingFormSchemaType = {
|
|||||||
totalQuantity?: number;
|
totalQuantity?: number;
|
||||||
maxTotalQuantity?: number; // original cap (hidden), helper
|
maxTotalQuantity?: number; // original cap (hidden), helper
|
||||||
|
|
||||||
kandangs: {
|
flockSourceKandangs: {
|
||||||
kandang: {
|
kandang: {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -22,6 +25,16 @@ type TransferToLayingFormSchemaType = {
|
|||||||
quantity: number | string; // editable
|
quantity: number | string; // editable
|
||||||
maxQuantity?: number; // original cap (hidden), helper
|
maxQuantity?: number; // original cap (hidden), helper
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
flockDestinationKandangs: {
|
||||||
|
kandang: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
quantity: number | string; // editable
|
||||||
|
maxQuantity?: number; // original cap (hidden), helper
|
||||||
|
}[];
|
||||||
|
|
||||||
reason?: string;
|
reason?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,7 +64,29 @@ export const TransferToLayingFormSchema: Yup.ObjectSchema<TransferToLayingFormSc
|
|||||||
.min(1, 'Jumlah transfer minimal 1')
|
.min(1, 'Jumlah transfer minimal 1')
|
||||||
.required('Jumlah transfer wajib diisi!'),
|
.required('Jumlah transfer wajib diisi!'),
|
||||||
|
|
||||||
kandangs: Yup.array()
|
flockSourceKandangs: Yup.array()
|
||||||
|
.of(
|
||||||
|
Yup.object({
|
||||||
|
kandang: Yup.object({
|
||||||
|
value: Yup.number().min(1).required(),
|
||||||
|
label: Yup.string().required(),
|
||||||
|
}).required('Kandang wajib diisi!'),
|
||||||
|
|
||||||
|
quantity: Yup.number()
|
||||||
|
.min(0, 'Kuantitas minimal 0!')
|
||||||
|
.max(
|
||||||
|
Yup.ref('maxQuantity'),
|
||||||
|
({ max }) => `Kuantitas maksimal ${max}!`
|
||||||
|
)
|
||||||
|
.required('Kuantitas wajib diisi!'),
|
||||||
|
|
||||||
|
maxQuantity: Yup.number().min(1).required(), // internal helper field
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1, 'Minimal 1 kandang terisi!')
|
||||||
|
.required('Kandang wajib diisi!'),
|
||||||
|
|
||||||
|
flockDestinationKandangs: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
kandang: Yup.object({
|
kandang: Yup.object({
|
||||||
@@ -81,3 +116,122 @@ export const UpdateTransferToLayingFormSchema = TransferToLayingFormSchema;
|
|||||||
export type TransferToLayingFormValues = Yup.InferType<
|
export type TransferToLayingFormValues = Yup.InferType<
|
||||||
typeof TransferToLayingFormSchema
|
typeof TransferToLayingFormSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const getTransferToLayingFormInitialValues = (
|
||||||
|
initialValues?: TransferToLaying
|
||||||
|
): TransferToLayingFormValues => {
|
||||||
|
return {
|
||||||
|
transfer_date: initialValues?.transfer_date
|
||||||
|
? formatDate(initialValues.transfer_date, 'YYYY-MM-DD')
|
||||||
|
: '',
|
||||||
|
flockSource: initialValues?.from_project_flock
|
||||||
|
? {
|
||||||
|
value: initialValues?.from_project_flock.id,
|
||||||
|
label: initialValues?.from_project_flock.flock_name,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
flockDestination: initialValues?.to_project_flock
|
||||||
|
? {
|
||||||
|
value: initialValues?.to_project_flock.id,
|
||||||
|
label: initialValues?.to_project_flock.flock_name,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
totalQuantity:
|
||||||
|
initialValues?.usage_qty ?? initialValues?.pending_usage_qty ?? undefined,
|
||||||
|
|
||||||
|
flockSourceKandangs: initialValues?.sources
|
||||||
|
? initialValues.sources.map((sourceKandang) => ({
|
||||||
|
kandang: {
|
||||||
|
value: sourceKandang.source_project_flock_kandang.kandang.id,
|
||||||
|
label: sourceKandang.source_project_flock_kandang.kandang.name,
|
||||||
|
},
|
||||||
|
quantity: sourceKandang.qty,
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
|
||||||
|
flockDestinationKandangs: initialValues?.targets
|
||||||
|
? initialValues.targets.map((targetKandang) => ({
|
||||||
|
kandang: {
|
||||||
|
value: targetKandang.target_project_flock_kandang.kandang.id,
|
||||||
|
label: targetKandang.target_project_flock_kandang.kandang.name,
|
||||||
|
},
|
||||||
|
quantity: targetKandang.qty,
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
|
||||||
|
reason: initialValues?.notes ?? undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFilledTransferToLayingFormInitialValues = async (
|
||||||
|
initialValues?: TransferToLaying
|
||||||
|
): Promise<TransferToLayingFormValues> => {
|
||||||
|
const mappedFlockSourceKandangsAvailableQty =
|
||||||
|
await TransferToLayingApi.getMappedFlockKandangsAvailability(
|
||||||
|
initialValues?.from_project_flock.id as number
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedFlockSourceKandangs = initialValues?.sources
|
||||||
|
? initialValues.sources.map((sourceKandang) => ({
|
||||||
|
kandang: {
|
||||||
|
value: sourceKandang.source_project_flock_kandang.kandang.id,
|
||||||
|
label: sourceKandang.source_project_flock_kandang.kandang.name,
|
||||||
|
},
|
||||||
|
quantity: sourceKandang.qty,
|
||||||
|
maxQuantity:
|
||||||
|
(mappedFlockSourceKandangsAvailableQty &&
|
||||||
|
mappedFlockSourceKandangsAvailableQty[
|
||||||
|
sourceKandang.source_project_flock_kandang.id
|
||||||
|
].available_qty) ??
|
||||||
|
0,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
let maxTotalQuantity = 0;
|
||||||
|
formattedFlockSourceKandangs.forEach((item) => {
|
||||||
|
maxTotalQuantity += item.maxQuantity;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
transfer_date: initialValues?.transfer_date
|
||||||
|
? formatDate(initialValues.transfer_date, 'YYYY-MM-DD')
|
||||||
|
: '',
|
||||||
|
flockSource: initialValues?.from_project_flock
|
||||||
|
? {
|
||||||
|
value: initialValues?.from_project_flock.id,
|
||||||
|
label: initialValues?.from_project_flock.flock_name,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
flockDestination: initialValues?.to_project_flock
|
||||||
|
? {
|
||||||
|
value: initialValues?.to_project_flock.id,
|
||||||
|
label: initialValues?.to_project_flock.flock_name,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
totalQuantity:
|
||||||
|
initialValues?.usage_qty ?? initialValues?.pending_usage_qty ?? undefined,
|
||||||
|
maxTotalQuantity: maxTotalQuantity,
|
||||||
|
|
||||||
|
flockSourceKandangs: formattedFlockSourceKandangs,
|
||||||
|
|
||||||
|
flockDestinationKandangs: initialValues?.targets
|
||||||
|
? initialValues.targets.map((targetKandang) => ({
|
||||||
|
kandang: {
|
||||||
|
value: targetKandang.target_project_flock_kandang.kandang.id,
|
||||||
|
label: targetKandang.target_project_flock_kandang.kandang.name,
|
||||||
|
},
|
||||||
|
quantity: targetKandang.qty,
|
||||||
|
|
||||||
|
// maxQuantity:
|
||||||
|
// targetKandang.target_project_flock_kandang.kandang.capacity,
|
||||||
|
|
||||||
|
// TODO: integrate this to real API kandang capacity
|
||||||
|
maxQuantity:
|
||||||
|
targetKandang.target_project_flock_kandang.kandang.capacity ??
|
||||||
|
Infinity,
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
|
||||||
|
reason: initialValues?.notes ?? undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
+404
-187
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
@@ -8,16 +8,23 @@ 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 TextInput from '@/components/input/TextInput';
|
|
||||||
import SelectInput, {
|
import SelectInput, {
|
||||||
OptionType,
|
OptionType,
|
||||||
// useSelect,
|
useSelect,
|
||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
import TextArea from '@/components/input/TextArea';
|
import TextArea from '@/components/input/TextArea';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
|
import ApprovalSteps, {
|
||||||
|
formatGroupedApprovalsToApprovalSteps,
|
||||||
|
} from '@/components/pages/ApprovalSteps';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
getFilledTransferToLayingFormInitialValues,
|
||||||
|
getTransferToLayingFormInitialValues,
|
||||||
TransferToLayingFormSchema,
|
TransferToLayingFormSchema,
|
||||||
TransferToLayingFormValues,
|
TransferToLayingFormValues,
|
||||||
UpdateTransferToLayingFormSchema,
|
UpdateTransferToLayingFormSchema,
|
||||||
@@ -31,6 +38,8 @@ import {
|
|||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||||
|
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||||
|
import { TRANSFER_TO_LAYING_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
|
|
||||||
interface TransferToLayingFormProps {
|
interface TransferToLayingFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -55,11 +64,23 @@ const TransferToLayingForm = ({
|
|||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
|
|
||||||
|
const { data: approvalHistory, isLoading: isLoadingApprovalHistory } = useSWR(
|
||||||
|
type === 'detail' && initialValues ? [String(initialValues.id)] : null,
|
||||||
|
([id]: string[]) => TransferToLayingApi.getApprovalHistory(Number(id))
|
||||||
|
);
|
||||||
|
|
||||||
const createTransferToLayingHandler = useCallback(
|
const createTransferToLayingHandler = useCallback(
|
||||||
async (payload: CreateTransferToLayingPayload) => {
|
async (payload: CreateTransferToLayingPayload) => {
|
||||||
console.log('Create transfer to laying:', { payload });
|
const createTransferToLayingRes =
|
||||||
|
await TransferToLayingApi.create(payload);
|
||||||
|
|
||||||
toast.success('Berhasil menambahkan data transfer ke laying!');
|
if (isResponseError(createTransferToLayingRes)) {
|
||||||
|
setFormErrorMessage(createTransferToLayingRes.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(createTransferToLayingRes?.message as string);
|
||||||
|
router.push('/production/transfer-to-laying');
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
@@ -69,46 +90,30 @@ const TransferToLayingForm = ({
|
|||||||
transferToLayingId: number,
|
transferToLayingId: number,
|
||||||
payload: UpdateTransferToLayingPayload
|
payload: UpdateTransferToLayingPayload
|
||||||
) => {
|
) => {
|
||||||
console.log(
|
const updateKandangRes = await TransferToLayingApi.update(
|
||||||
`Update transfer to laying with ID of ${transferToLayingId}:`,
|
transferToLayingId,
|
||||||
{ payload }
|
payload
|
||||||
);
|
);
|
||||||
|
|
||||||
toast.success('Berhasil mengubah data transfer ke laying!');
|
if (updateKandangRes?.status === 'error') {
|
||||||
|
setFormErrorMessage(updateKandangRes.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(updateKandangRes?.message as string);
|
||||||
|
router.refresh();
|
||||||
|
router.push('/production/transfer-to-laying');
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const formikInitialValues = useMemo<TransferToLayingFormValues>(() => {
|
// const formikInitialValues = useMemo<TransferToLayingFormValues>(() => {
|
||||||
return {
|
// return getTransferToLayingFormInitialValues(initialValues);
|
||||||
transfer_date: initialValues?.transfer_date ?? '',
|
// }, [initialValues]);
|
||||||
flockSource: initialValues?.flock_source
|
|
||||||
? {
|
|
||||||
value: initialValues?.flock_source.id,
|
|
||||||
label: initialValues?.flock_source.name,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
flockDestination: initialValues?.flock_destination
|
|
||||||
? {
|
|
||||||
value: initialValues?.flock_destination.id,
|
|
||||||
label: initialValues?.flock_destination.name,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
totalQuantity: initialValues?.quantity ?? undefined,
|
|
||||||
|
|
||||||
kandangs: initialValues?.kandangs
|
const [formikInitialValues, setFormikInitialValues] = useState(
|
||||||
? initialValues.kandangs.map((kandang) => ({
|
getTransferToLayingFormInitialValues()
|
||||||
kandang: {
|
);
|
||||||
value: kandang.kandang.id,
|
|
||||||
label: kandang.kandang.name,
|
|
||||||
},
|
|
||||||
quantity: kandang.quantity,
|
|
||||||
}))
|
|
||||||
: [],
|
|
||||||
|
|
||||||
reason: initialValues?.reason ?? undefined,
|
|
||||||
};
|
|
||||||
}, [initialValues]);
|
|
||||||
|
|
||||||
const formik = useFormik<TransferToLayingFormValues>({
|
const formik = useFormik<TransferToLayingFormValues>({
|
||||||
initialValues: formikInitialValues,
|
initialValues: formikInitialValues,
|
||||||
@@ -117,23 +122,23 @@ const TransferToLayingForm = ({
|
|||||||
? UpdateTransferToLayingFormSchema
|
? UpdateTransferToLayingFormSchema
|
||||||
: TransferToLayingFormSchema,
|
: TransferToLayingFormSchema,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
console.log({ values });
|
|
||||||
|
|
||||||
setFormErrorMessage('');
|
setFormErrorMessage('');
|
||||||
|
|
||||||
const transferToLayingPayload: CreateTransferToLayingPayload = {
|
const transferToLayingPayload: CreateTransferToLayingPayload = {
|
||||||
transfer_date: values.transfer_date as string,
|
transfer_date: values.transfer_date as string,
|
||||||
flock_source_id: values.flockSource?.value as number,
|
source_project_flock_id: values.flockSource?.value as number,
|
||||||
flock_destination_id: values.flockDestination?.value as number,
|
target_project_flock_id: values.flockDestination?.value as number,
|
||||||
totalQuantity: values.totalQuantity as number,
|
totalQuantity: values.totalQuantity as number,
|
||||||
|
|
||||||
kandangs: values.kandangs?.map((kandang) => ({
|
source_kandangs: values.flockSourceKandangs?.map((kandang) => ({
|
||||||
kandang_id: kandang.kandang.value,
|
project_flock_kandang_id: kandang.kandang.value,
|
||||||
quantity: kandang.quantity,
|
quantity: parseFloat(kandang.quantity as string),
|
||||||
})) as {
|
})) as CreateTransferToLayingPayload['source_kandangs'],
|
||||||
kandang_id: number;
|
|
||||||
quantity: number;
|
target_kandangs: values.flockDestinationKandangs?.map((kandang) => ({
|
||||||
}[],
|
project_flock_kandang_id: kandang.kandang.value,
|
||||||
|
quantity: parseFloat(kandang.quantity as string),
|
||||||
|
})) as CreateTransferToLayingPayload['target_kandangs'],
|
||||||
|
|
||||||
reason: values.reason as string,
|
reason: values.reason as string,
|
||||||
};
|
};
|
||||||
@@ -154,7 +159,11 @@ const TransferToLayingForm = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { setValues: formikSetValues, values: formikValues } = formik;
|
const { setValues: formikSetValues, values: formikValues } = formik;
|
||||||
const { kandangs: kandangsValue } = formikValues;
|
const {
|
||||||
|
flockSourceKandangs: flockSourceKandangsValue,
|
||||||
|
flockDestinationKandangs: flockDestinationKandangsValue,
|
||||||
|
totalQuantity,
|
||||||
|
} = formikValues;
|
||||||
|
|
||||||
const deleteTransferToLayingClickHandler = () => {
|
const deleteTransferToLayingClickHandler = () => {
|
||||||
deleteModal.openModal();
|
deleteModal.openModal();
|
||||||
@@ -172,24 +181,32 @@ const TransferToLayingForm = ({
|
|||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
// TODO: delete data and integrate to real API
|
try {
|
||||||
deleteModal.closeModal();
|
await TransferToLayingApi.delete(initialValues?.id as number);
|
||||||
toast.success('Berhasil menghapus data transfer ke laying!');
|
|
||||||
|
|
||||||
setIsDeleteLoading(false);
|
toast.success('Berhasil menghapus data transfer ke laying!');
|
||||||
|
router.push('/production/transfer-to-laying');
|
||||||
|
} catch (error) {
|
||||||
|
toast.success('Gagal menghapus data transfer ke laying!');
|
||||||
|
} finally {
|
||||||
|
deleteModal.closeModal();
|
||||||
|
setIsDeleteLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmationModalApproveClickHandler = async () => {
|
const confirmationModalApproveClickHandler = async (notes: string) => {
|
||||||
setIsApproveLoading(true);
|
setIsApproveLoading(true);
|
||||||
|
|
||||||
const approveResponse = await TransferToLayingApi.approve(
|
const approveResponse = await TransferToLayingApi.approve(
|
||||||
initialValues?.id as number
|
initialValues?.id as number,
|
||||||
|
notes
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isResponseSuccess(approveResponse)) {
|
if (isResponseSuccess(approveResponse)) {
|
||||||
approveModal.closeModal();
|
approveModal.closeModal();
|
||||||
|
|
||||||
toast.success('Berhasil approve data transfer ke laying!');
|
toast.success('Berhasil approve data transfer ke laying!');
|
||||||
|
router.push('/production/transfer-to-laying');
|
||||||
} else {
|
} else {
|
||||||
approveModal.closeModal();
|
approveModal.closeModal();
|
||||||
|
|
||||||
@@ -199,17 +216,19 @@ const TransferToLayingForm = ({
|
|||||||
setIsApproveLoading(false);
|
setIsApproveLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmationModalRejectClickHandler = async () => {
|
const confirmationModalRejectClickHandler = async (notes: string) => {
|
||||||
setIsRejectLoading(true);
|
setIsRejectLoading(true);
|
||||||
|
|
||||||
const rejectResponse = await TransferToLayingApi.reject(
|
const rejectResponse = await TransferToLayingApi.reject(
|
||||||
initialValues?.id as number
|
initialValues?.id as number,
|
||||||
|
notes
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isResponseSuccess(rejectResponse)) {
|
if (isResponseSuccess(rejectResponse)) {
|
||||||
rejectModal.closeModal();
|
rejectModal.closeModal();
|
||||||
|
|
||||||
toast.success('Berhasil reject data transfer ke laying!');
|
toast.success('Berhasil reject data transfer ke laying!');
|
||||||
|
router.push('/production/transfer-to-laying');
|
||||||
} else {
|
} else {
|
||||||
rejectModal.closeModal();
|
rejectModal.closeModal();
|
||||||
|
|
||||||
@@ -219,49 +238,47 @@ const TransferToLayingForm = ({
|
|||||||
setIsRejectLoading(false);
|
setIsRejectLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isRepeaterInputError = (
|
// flock source
|
||||||
column: keyof TransferToLayingFormValues['kandangs'][0],
|
const isFlockSourceKandangsRepeaterInputError = (
|
||||||
|
column: keyof TransferToLayingFormValues['flockSourceKandangs'][0],
|
||||||
idx: number
|
idx: number
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
formik.touched.kandangs?.[idx]?.[column] &&
|
formik.touched.flockSourceKandangs?.[idx]?.[column] &&
|
||||||
Boolean(
|
Boolean(
|
||||||
formik.errors.kandangs?.[idx] instanceof Object &&
|
formik.errors.flockSourceKandangs?.[idx] instanceof Object &&
|
||||||
formik.errors.kandangs?.[idx]?.[column]
|
formik.errors.flockSourceKandangs?.[idx]?.[column]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const repeaterInputErrorMessage = (
|
const flockSourceKandangsRepeaterInputErrorMessage = (
|
||||||
column: keyof TransferToLayingFormValues['kandangs'][0],
|
column: keyof TransferToLayingFormValues['flockSourceKandangs'][0],
|
||||||
idx: number
|
idx: number
|
||||||
) => {
|
) => {
|
||||||
return (formik.errors.kandangs?.[idx] as Record<string, string>)?.[column];
|
return (
|
||||||
|
formik.errors.flockSourceKandangs?.[idx] as Record<string, string>
|
||||||
|
)?.[column];
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: remove dummy data and use real data
|
const {
|
||||||
// Flock Source
|
setInputValue: setFlockSourceInputValue,
|
||||||
// const {
|
options: flockSourceOptions,
|
||||||
// inputValue: flockSourceInputValue,
|
isLoadingOptions: isLoadingFlockSourceOptions,
|
||||||
// setInputValue: setFlockSourceInputValue,
|
rawData: flockSources,
|
||||||
// options: flockSourceOptions,
|
} = useSelect<ProjectFlock>(
|
||||||
// isLoadingOptions: isLoadingFlockSourceOptions,
|
'/production/project-flocks',
|
||||||
// } = useSelect<FlockWithKandangs>('/transfer-to-laying/production/get-flock-source', 'id', 'name');
|
'id',
|
||||||
|
'flock_name',
|
||||||
// TODO: remove this dummy data
|
'search',
|
||||||
const { data: flockSources, isLoading: isLoadingFlockSourceOptions } = useSWR(
|
{
|
||||||
'test',
|
category: 'GROWING',
|
||||||
() => TransferToLayingApi.getFlockSource()
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const flockSourceOptions = isResponseSuccess(flockSources)
|
const flockSourceChangeHandler = async (
|
||||||
? flockSources?.data.map((flockSource) => ({
|
val: OptionType | OptionType[] | null
|
||||||
value: flockSource.id,
|
) => {
|
||||||
label: flockSource.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const flockSourceChangeHandler = (val: OptionType | OptionType[] | null) => {
|
|
||||||
// Get flock source data for total quantity and kandang
|
// Get flock source data for total quantity and kandang
|
||||||
const flockSource =
|
const flockSource =
|
||||||
isResponseSuccess(flockSources) && val !== null
|
isResponseSuccess(flockSources) && val !== null
|
||||||
@@ -272,21 +289,38 @@ const TransferToLayingForm = ({
|
|||||||
|
|
||||||
// Set total quantity and kandangs
|
// Set total quantity and kandangs
|
||||||
if (flockSource) {
|
if (flockSource) {
|
||||||
|
const mappedFlockKandangsAvailableQty =
|
||||||
|
await TransferToLayingApi.getMappedFlockKandangsAvailability(
|
||||||
|
flockSource.id
|
||||||
|
);
|
||||||
|
|
||||||
const formattedKandangs = flockSource.kandangs.map((item) => ({
|
const formattedKandangs = flockSource.kandangs.map((item) => ({
|
||||||
kandang: {
|
kandang: {
|
||||||
value: item.kandang.id,
|
value: item.project_flock_kandang_id,
|
||||||
label: item.kandang.name,
|
label: item.name,
|
||||||
},
|
},
|
||||||
quantity: '',
|
quantity: '',
|
||||||
maxQuantity: item.quantity,
|
maxQuantity:
|
||||||
|
(mappedFlockKandangsAvailableQty &&
|
||||||
|
mappedFlockKandangsAvailableQty[item.project_flock_kandang_id]
|
||||||
|
.available_qty) ??
|
||||||
|
0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
formik.setFieldValue('totalQuantity', flockSource.totalQuantity);
|
let maxTotalQuantity = 0;
|
||||||
formik.setFieldValue('maxTotalQuantity', flockSource.totalQuantity);
|
// flockSource.kandangs.forEach((item) => {
|
||||||
formik.setFieldValue('kandangs', formattedKandangs);
|
// maxTotalQuantity += item.capacity;
|
||||||
|
// });
|
||||||
|
formattedKandangs.forEach((item) => {
|
||||||
|
maxTotalQuantity += item.maxQuantity;
|
||||||
|
});
|
||||||
|
|
||||||
|
formik.setFieldValue('totalQuantity', '');
|
||||||
|
formik.setFieldValue('maxTotalQuantity', maxTotalQuantity);
|
||||||
|
formik.setFieldValue('flockSourceKandangs', formattedKandangs);
|
||||||
} else {
|
} else {
|
||||||
formik.setFieldValue('totalQuantity', undefined);
|
formik.setFieldValue('totalQuantity', undefined);
|
||||||
formik.setFieldValue('kandangs', undefined);
|
formik.setFieldValue('flockSourceKandangs', undefined);
|
||||||
formik.setFieldValue('reason', '');
|
formik.setFieldValue('reason', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,52 +328,137 @@ const TransferToLayingForm = ({
|
|||||||
formik.setFieldValue('flockSource', val);
|
formik.setFieldValue('flockSource', val);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: remove dummy data and use real data
|
// flock destination
|
||||||
// Flock Destination
|
const isFlockDestinationKandangsRepeaterInputError = (
|
||||||
// const {
|
column: keyof TransferToLayingFormValues['flockDestinationKandangs'][0],
|
||||||
// inputValue: flockDestinationInputValue,
|
idx: number
|
||||||
// setInputValue: setFlockDestinationInputValue,
|
) => {
|
||||||
// options: flockDestinationOptions,
|
return (
|
||||||
// isLoadingOptions: isLoadingFlockDestinationOptions,
|
formik.touched.flockDestinationKandangs?.[idx]?.[column] &&
|
||||||
// } = useSelect<FlockWithKandangs>('/transfer-to-laying/production/get-flock-destination', 'id', 'name');
|
Boolean(
|
||||||
|
formik.errors.flockDestinationKandangs?.[idx] instanceof Object &&
|
||||||
|
formik.errors.flockDestinationKandangs?.[idx]?.[column]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const flockDestinationKandangsRepeaterInputErrorMessage = (
|
||||||
|
column: keyof TransferToLayingFormValues['flockDestinationKandangs'][0],
|
||||||
|
idx: number
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
formik.errors.flockDestinationKandangs?.[idx] as Record<string, string>
|
||||||
|
)?.[column];
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: remove this dummy data
|
|
||||||
const {
|
const {
|
||||||
data: flockDestinations,
|
setInputValue: setFlockDestinationInputValue,
|
||||||
isLoading: isLoadingFlockDestinationOptions,
|
options: flockDestinationOptions,
|
||||||
} = useSWR('test', () => TransferToLayingApi.getFlockSource());
|
isLoadingOptions: isLoadingFlockDestinationOptions,
|
||||||
|
rawData: flockDestinations,
|
||||||
const flockDestinationOptions = isResponseSuccess(flockDestinations)
|
} = useSelect<ProjectFlock>(
|
||||||
? flockDestinations?.data.map((flockDestination) => ({
|
'/production/project-flocks',
|
||||||
value: flockDestination.id,
|
'id',
|
||||||
label: flockDestination.name,
|
'flock_name',
|
||||||
}))
|
'search',
|
||||||
: [];
|
{
|
||||||
|
category: 'LAYING',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const flockDestinationChangeHandler = (
|
const flockDestinationChangeHandler = (
|
||||||
val: OptionType | OptionType[] | null
|
val: OptionType | OptionType[] | null
|
||||||
) => {
|
) => {
|
||||||
|
// Get flock destination data for total quantity and kandang
|
||||||
|
const flockDestination =
|
||||||
|
isResponseSuccess(flockDestinations) && val !== null
|
||||||
|
? flockDestinations.data.find(
|
||||||
|
(item) => item.id === (val as OptionType).value
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// Set total quantity and kandangs
|
||||||
|
if (flockDestination) {
|
||||||
|
const formattedKandangs = flockDestination.kandangs.map((item) => ({
|
||||||
|
kandang: {
|
||||||
|
value: item.project_flock_kandang_id,
|
||||||
|
label: item.name,
|
||||||
|
},
|
||||||
|
quantity: '',
|
||||||
|
|
||||||
|
// TODO: integrate this later to real kandang capacity API
|
||||||
|
// maxQuantity: item.capacity ?? 0,
|
||||||
|
maxQuantity: item.capacity ?? Infinity,
|
||||||
|
}));
|
||||||
|
|
||||||
|
formik.setFieldValue('flockDestinationKandangs', formattedKandangs);
|
||||||
|
}
|
||||||
|
|
||||||
formik.setFieldTouched('flockDestination', true);
|
formik.setFieldTouched('flockDestination', true);
|
||||||
formik.setFieldValue('flockDestination', val);
|
formik.setFieldValue('flockDestination', val);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isShowApproveRejectButton =
|
||||||
|
initialValues &&
|
||||||
|
initialValues?.approval?.step_number === 1 &&
|
||||||
|
initialValues?.approval.action !== 'REJECTED';
|
||||||
|
|
||||||
|
const isShowDeleteButton =
|
||||||
|
initialValues &&
|
||||||
|
initialValues?.approval.action !== 'REJECTED' &&
|
||||||
|
initialValues?.approval.action !== 'APPROVED';
|
||||||
|
|
||||||
|
const isShowEditButton = isShowDeleteButton;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getFilledInitialValues = async () => {
|
||||||
|
if (initialValues) {
|
||||||
|
const filledInitialValues =
|
||||||
|
await getFilledTransferToLayingFormInitialValues(initialValues);
|
||||||
|
|
||||||
|
setFormikInitialValues(filledInitialValues);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getFilledInitialValues();
|
||||||
|
}, [initialValues, setFormikInitialValues]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formikSetValues(formikInitialValues);
|
formikSetValues(formikInitialValues);
|
||||||
}, [formikSetValues, formikInitialValues]);
|
}, [formikSetValues, formikInitialValues]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// calculate total quantity if kandangs quantity change
|
// calculate total quantity if kandangs quantity change
|
||||||
if (kandangsValue && kandangsValue.length > 0) {
|
if (flockSourceKandangsValue && flockSourceKandangsValue.length > 0) {
|
||||||
let newTotalQuantity = 0;
|
let newTotalQuantity = 0;
|
||||||
|
|
||||||
kandangsValue.forEach((item) => {
|
flockSourceKandangsValue.forEach((item) => {
|
||||||
newTotalQuantity += item.quantity as number;
|
newTotalQuantity += parseFloat(item.quantity as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
formik.setFieldValue('totalQuantity', newTotalQuantity);
|
formik.setFieldValue('totalQuantity', newTotalQuantity);
|
||||||
formik.validateField('totalQuantity');
|
formik.validateField('totalQuantity');
|
||||||
}
|
}
|
||||||
}, [formikSetValues, kandangsValue]);
|
}, [formikSetValues, flockSourceKandangsValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// calculate total quantity if kandangs quantity change
|
||||||
|
if (
|
||||||
|
flockDestinationKandangsValue &&
|
||||||
|
flockDestinationKandangsValue.length > 0
|
||||||
|
) {
|
||||||
|
let destinationKandangsTotalQuantity = 0;
|
||||||
|
|
||||||
|
flockDestinationKandangsValue.forEach((item) => {
|
||||||
|
destinationKandangsTotalQuantity += parseFloat(item.quantity as string);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
destinationKandangsTotalQuantity > parseFloat(String(totalQuantity))
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [formikSetValues, flockDestinationKandangsValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -361,30 +480,56 @@ const TransferToLayingForm = ({
|
|||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className='w-full my-4 flex flex-row justify-end gap-2'>
|
{type === 'detail' &&
|
||||||
|
initialValues &&
|
||||||
|
!isLoadingApprovalHistory &&
|
||||||
|
isResponseSuccess(approvalHistory) && (
|
||||||
|
<div className='w-full my-4'>
|
||||||
|
<ApprovalSteps
|
||||||
|
approvals={formatGroupedApprovalsToApprovalSteps(
|
||||||
|
TRANSFER_TO_LAYING_APPROVAL_LINE,
|
||||||
|
approvalHistory.data,
|
||||||
|
initialValues.approval
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='w-full my-4 flex flex-row justify-between gap-2'>
|
||||||
{type === 'detail' && (
|
{type === 'detail' && (
|
||||||
<>
|
<>
|
||||||
<Button
|
{isShowApproveRejectButton && (
|
||||||
variant='outline'
|
<div className='w-full flex flex-row justify-end gap-2'>
|
||||||
color='success'
|
{/* TODO: apply RBAC */}
|
||||||
onClick={approveClickHandler}
|
<Button
|
||||||
// disabled={selectedRowIds.length === 0}
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='success'
|
||||||
>
|
onClick={approveClickHandler}
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
className='w-full sm:w-fit'
|
||||||
Approve
|
>
|
||||||
</Button>
|
<Icon
|
||||||
|
icon='material-symbols:check'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='error'
|
color='error'
|
||||||
onClick={rejectClickHandler}
|
onClick={rejectClickHandler}
|
||||||
// disabled={selectedRowIds.length === 0}
|
className='w-full sm:w-fit'
|
||||||
className='w-full sm:w-fit'
|
>
|
||||||
>
|
<Icon
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
icon='material-symbols:close'
|
||||||
Reject
|
width={24}
|
||||||
</Button>
|
height={24}
|
||||||
|
/>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -395,13 +540,12 @@ const TransferToLayingForm = ({
|
|||||||
className='w-full flex flex-col gap-6'
|
className='w-full flex flex-col gap-6'
|
||||||
>
|
>
|
||||||
<div className='flex flex-col gap-4'>
|
<div className='flex flex-col gap-4'>
|
||||||
<TextInput
|
<DateInput
|
||||||
required
|
required
|
||||||
type='date'
|
|
||||||
label='Tanggal Transfer'
|
label='Tanggal Transfer'
|
||||||
name='transfer_date'
|
name='transfer_date'
|
||||||
placeholder='Masukkan tanggal transfer'
|
placeholder='Masukkan tanggal transfer'
|
||||||
value={formik.values.transfer_date}
|
value={formik.values.transfer_date ?? ''}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
isError={
|
isError={
|
||||||
@@ -421,7 +565,7 @@ const TransferToLayingForm = ({
|
|||||||
options={flockSourceOptions}
|
options={flockSourceOptions}
|
||||||
onChange={flockSourceChangeHandler}
|
onChange={flockSourceChangeHandler}
|
||||||
isLoading={isLoadingFlockSourceOptions}
|
isLoading={isLoadingFlockSourceOptions}
|
||||||
// onInputChange={setFlockSourceInputValue}
|
onInputChange={setFlockSourceInputValue}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.flockSource &&
|
formik.touched.flockSource &&
|
||||||
Boolean(typeof formik.errors.flockSource === 'string')
|
Boolean(typeof formik.errors.flockSource === 'string')
|
||||||
@@ -439,7 +583,7 @@ const TransferToLayingForm = ({
|
|||||||
options={flockDestinationOptions}
|
options={flockDestinationOptions}
|
||||||
onChange={flockDestinationChangeHandler}
|
onChange={flockDestinationChangeHandler}
|
||||||
isLoading={isLoadingFlockDestinationOptions}
|
isLoading={isLoadingFlockDestinationOptions}
|
||||||
// onInputChange={setFlockDestinationInputValue}
|
onInputChange={setFlockDestinationInputValue}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.flockDestination &&
|
formik.touched.flockDestination &&
|
||||||
Boolean(typeof formik.errors.flockDestination === 'string')
|
Boolean(typeof formik.errors.flockDestination === 'string')
|
||||||
@@ -450,9 +594,8 @@ const TransferToLayingForm = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TextInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
type='number'
|
|
||||||
name='totalQuantity'
|
name='totalQuantity'
|
||||||
label='Jumlah Transfer'
|
label='Jumlah Transfer'
|
||||||
bottomLabel={
|
bottomLabel={
|
||||||
@@ -461,7 +604,9 @@ const TransferToLayingForm = ({
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
placeholder='Masukkan jumlah transfer'
|
placeholder='Masukkan jumlah transfer'
|
||||||
value={formik.values.totalQuantity ?? ''}
|
value={
|
||||||
|
formik.values.totalQuantity ? formik.values.totalQuantity : ''
|
||||||
|
}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
isError={
|
isError={
|
||||||
@@ -469,24 +614,22 @@ const TransferToLayingForm = ({
|
|||||||
Boolean(formik.errors.totalQuantity)
|
Boolean(formik.errors.totalQuantity)
|
||||||
}
|
}
|
||||||
errorMessage={formik.errors.totalQuantity}
|
errorMessage={formik.errors.totalQuantity}
|
||||||
// readOnly={type === 'detail'}
|
|
||||||
// disabled={Boolean(formik.errors.flockSource)}
|
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div className='flex flex-col gap-4'>
|
||||||
<div className='overflow-x-auto'>
|
<div className='overflow-x-auto'>
|
||||||
<table className='table'>
|
<table className='table'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Kandang</th>
|
<th>Kandang Flock Asal</th>
|
||||||
<th>Kuantitas</th>
|
<th>Kuantitas</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{(!formik.values.kandangs ||
|
{(!formik.values.flockSourceKandangs ||
|
||||||
formik.values.kandangs.length === 0) && (
|
formik.values.flockSourceKandangs.length === 0) && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={2}>
|
<td colSpan={2}>
|
||||||
<p className='w-full text-center text-gray-400'>
|
<p className='w-full text-center text-gray-400'>
|
||||||
@@ -496,8 +639,8 @@ const TransferToLayingForm = ({
|
|||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{formik.values.kandangs &&
|
{formik.values.flockSourceKandangs &&
|
||||||
formik.values.kandangs.map((kandang, idx) => (
|
formik.values.flockSourceKandangs.map((kandang, idx) => (
|
||||||
<tr key={idx}>
|
<tr key={idx}>
|
||||||
<td>
|
<td>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
@@ -511,10 +654,9 @@ const TransferToLayingForm = ({
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<TextInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
type='number'
|
name={`flockSourceKandangs[${idx}].quantity`}
|
||||||
name={`kandangs[${idx}].quantity`}
|
|
||||||
bottomLabel={
|
bottomLabel={
|
||||||
kandang.maxQuantity
|
kandang.maxQuantity
|
||||||
? `Max: ${kandang.maxQuantity}`
|
? `Max: ${kandang.maxQuantity}`
|
||||||
@@ -524,8 +666,11 @@ const TransferToLayingForm = ({
|
|||||||
value={kandang.quantity}
|
value={kandang.quantity}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
isError={isRepeaterInputError('quantity', idx)}
|
isError={isFlockSourceKandangsRepeaterInputError(
|
||||||
errorMessage={repeaterInputErrorMessage(
|
'quantity',
|
||||||
|
idx
|
||||||
|
)}
|
||||||
|
errorMessage={flockSourceKandangsRepeaterInputErrorMessage(
|
||||||
'quantity',
|
'quantity',
|
||||||
idx
|
idx
|
||||||
)}
|
)}
|
||||||
@@ -540,6 +685,76 @@ const TransferToLayingForm = ({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='overflow-x-auto'>
|
||||||
|
<table className='table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Kandang Flock Tujuan</th>
|
||||||
|
<th>Kuantitas</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{(!formik.values.flockDestinationKandangs ||
|
||||||
|
formik.values.flockDestinationKandangs.length === 0) && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={2}>
|
||||||
|
<p className='w-full text-center text-gray-400'>
|
||||||
|
Pilih flock tujuan terlebih dahulu!
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formik.values.flockDestinationKandangs &&
|
||||||
|
formik.values.flockDestinationKandangs.map(
|
||||||
|
(kandang, idx) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td>
|
||||||
|
<SelectInput
|
||||||
|
value={kandang.kandang}
|
||||||
|
options={[]}
|
||||||
|
isDisabled
|
||||||
|
className={{
|
||||||
|
wrapper: 'min-w-52',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
name={`flockDestinationKandangs[${idx}].quantity`}
|
||||||
|
bottomLabel={
|
||||||
|
kandang.maxQuantity
|
||||||
|
? `Max: ${kandang.maxQuantity}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
placeholder='Masukkan kuantitas'
|
||||||
|
value={kandang.quantity}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
isError={isFlockDestinationKandangsRepeaterInputError(
|
||||||
|
'quantity',
|
||||||
|
idx
|
||||||
|
)}
|
||||||
|
errorMessage={flockDestinationKandangsRepeaterInputErrorMessage(
|
||||||
|
'quantity',
|
||||||
|
idx
|
||||||
|
)}
|
||||||
|
readOnly={type === 'detail'}
|
||||||
|
className={{
|
||||||
|
wrapper: 'min-w-52',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TextArea
|
<TextArea
|
||||||
@@ -558,25 +773,38 @@ const TransferToLayingForm = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{formErrorMessage && (
|
||||||
|
<div role='alert' className='alert alert-error w-full'>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:error-outline'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
<span>{formErrorMessage}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<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
|
{isShowDeleteButton && (
|
||||||
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>
|
||||||
|
)}
|
||||||
|
|
||||||
{type !== 'edit' && (
|
{type !== 'edit' && isShowEditButton && (
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='warning'
|
color='warning'
|
||||||
@@ -617,17 +845,6 @@ const TransferToLayingForm = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{formErrorMessage && (
|
|
||||||
<div role='alert' className='alert alert-error'>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:error-outline'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
/>
|
|
||||||
<span>{formErrorMessage}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -650,7 +867,7 @@ const TransferToLayingForm = ({
|
|||||||
|
|
||||||
{type === 'detail' && (
|
{type === 'detail' && (
|
||||||
<>
|
<>
|
||||||
<ConfirmationModal
|
<ConfirmationModalWithNotes
|
||||||
ref={approveModal.ref}
|
ref={approveModal.ref}
|
||||||
type='success'
|
type='success'
|
||||||
text='Apakah anda yakin ingin approve data transfer ke laying ini?'
|
text='Apakah anda yakin ingin approve data transfer ke laying ini?'
|
||||||
@@ -665,7 +882,7 @@ const TransferToLayingForm = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModalWithNotes
|
||||||
ref={rejectModal.ref}
|
ref={rejectModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
text='Apakah anda yakin ingin reject data transfer ke laying ini?'
|
text='Apakah anda yakin ingin reject data transfer ke laying ini?'
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ export const PROJECT_FLOCK_APPROVAL_LINE: ApprovalLine = [
|
|||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
export const TRANSFER_TO_LAYING_APPROVAL_LINE: ApprovalLine = [
|
||||||
|
{
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Pengajuan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Disetujui',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const RECORDING_APPROVAL_LINE: ApprovalLine = [
|
export const RECORDING_APPROVAL_LINE: ApprovalLine = [
|
||||||
{
|
{
|
||||||
step_number: 1,
|
step_number: 1,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Vendored
-12
@@ -12,15 +12,3 @@ export type CreateFlockPayload = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateFlockPayload = CreateFlockPayload;
|
export type UpdateFlockPayload = CreateFlockPayload;
|
||||||
|
|
||||||
// ---------------------------------------
|
|
||||||
// TODO: adjust this later after Transfer to Laying API done
|
|
||||||
import { BaseKandang } from '@/types/api/master-data/kandang';
|
|
||||||
|
|
||||||
export type FlockWithKandangs = BaseFlock & {
|
|
||||||
totalQuantity: number;
|
|
||||||
kandangs: {
|
|
||||||
kandang: BaseKandang;
|
|
||||||
quantity: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|||||||
+1
@@ -8,6 +8,7 @@ export type BaseKandang = {
|
|||||||
status: string;
|
status: string;
|
||||||
location: BaseLocation;
|
location: BaseLocation;
|
||||||
pic: BaseUser;
|
pic: BaseUser;
|
||||||
|
capacity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Kandang = BaseMetadata & BaseKandang;
|
export type Kandang = BaseMetadata & BaseKandang;
|
||||||
|
|||||||
+15
-2
@@ -7,7 +7,8 @@ import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
|
|||||||
|
|
||||||
export type BaseProjectFlock = {
|
export type BaseProjectFlock = {
|
||||||
id: number;
|
id: number;
|
||||||
flock_name: string;
|
name?: string;
|
||||||
|
flock_name?: string;
|
||||||
status: string;
|
status: string;
|
||||||
flock: Flock;
|
flock: Flock;
|
||||||
flock_id: number;
|
flock_id: number;
|
||||||
@@ -20,7 +21,9 @@ export type BaseProjectFlock = {
|
|||||||
location_id: number;
|
location_id: number;
|
||||||
period: number;
|
period: number;
|
||||||
kandang_ids: number[];
|
kandang_ids: number[];
|
||||||
kandangs: Kandang[];
|
kandangs: (Kandang & {
|
||||||
|
project_flock_kandang_id: number;
|
||||||
|
})[];
|
||||||
approval: BaseApproval;
|
approval: BaseApproval;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,3 +60,13 @@ export type ProjectFlockKandangLookup = {
|
|||||||
project_flock: ProjectFlock;
|
project_flock: ProjectFlock;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ProjectFlockAvailableQuantity = {
|
||||||
|
project_flock_id: number;
|
||||||
|
flock_name: string;
|
||||||
|
category: 'LAYING' | 'GROWING';
|
||||||
|
kandangs: {
|
||||||
|
project_flock_kandang_id: number;
|
||||||
|
available_qty: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|||||||
+69
-15
@@ -1,34 +1,88 @@
|
|||||||
import { BaseApiResponse, BaseMetadata, flags } from '@/types/api/api-general';
|
import {
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
BaseApiResponse,
|
||||||
|
BaseMetadata,
|
||||||
|
CreatedUser,
|
||||||
|
flags,
|
||||||
|
} from '@/types/api/api-general';
|
||||||
|
import { BaseKandang, Kandang } from '@/types/api/master-data/kandang';
|
||||||
|
import { WarehouseType } from '@/types/api/master-data/warehouse';
|
||||||
|
|
||||||
export type BaseTransferToLaying = {
|
export type BaseTransferToLaying = {
|
||||||
id: number;
|
id: number;
|
||||||
|
transfer_number: string;
|
||||||
transfer_date: string;
|
transfer_date: string;
|
||||||
flock_source: {
|
notes: string;
|
||||||
|
from_project_flock: {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
flock_name: string;
|
||||||
|
category: 'GROWING' | 'LAYING';
|
||||||
};
|
};
|
||||||
flock_destination: {
|
to_project_flock: {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
flock_name: string;
|
||||||
|
category: 'GROWING' | 'LAYING';
|
||||||
};
|
};
|
||||||
quantity: number;
|
pending_usage_qty: number | null;
|
||||||
kandangs: {
|
usage_qty: number | null;
|
||||||
kandang: Kandang;
|
|
||||||
quantity: number;
|
sources: {
|
||||||
|
source_project_flock_kandang: {
|
||||||
|
id: number;
|
||||||
|
kandang: Omit<BaseKandang, 'status' | 'location' | 'pic'>;
|
||||||
|
};
|
||||||
|
qty: number;
|
||||||
|
product_warehouse: {
|
||||||
|
product: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
warehouse: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
type: WarehouseType;
|
||||||
|
};
|
||||||
|
};
|
||||||
}[];
|
}[];
|
||||||
reason: string;
|
|
||||||
|
targets: {
|
||||||
|
target_project_flock_kandang: {
|
||||||
|
id: number;
|
||||||
|
kandang: Omit<BaseKandang, 'status' | 'location' | 'pic'>;
|
||||||
|
};
|
||||||
|
qty: number;
|
||||||
|
product_warehouse: {
|
||||||
|
product: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
warehouse: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
type: WarehouseType;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
|
||||||
|
created_by: number;
|
||||||
|
created_user: CreatedUser;
|
||||||
|
created_at: string;
|
||||||
|
|
||||||
|
approval: BaseApproval;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TransferToLaying = BaseMetadata & BaseTransferToLaying;
|
export type TransferToLaying = BaseMetadata & BaseTransferToLaying;
|
||||||
|
|
||||||
export type CreateTransferToLayingPayload = {
|
export type CreateTransferToLayingPayload = {
|
||||||
transfer_date: string;
|
transfer_date: string;
|
||||||
flock_source_id: number;
|
source_project_flock_id: number;
|
||||||
flock_destination_id: number;
|
target_project_flock_id: number;
|
||||||
totalQuantity: number;
|
totalQuantity: number;
|
||||||
kandangs: {
|
source_kandangs: {
|
||||||
kandang_id: number;
|
project_flock_kandang_id: number;
|
||||||
|
quantity: number;
|
||||||
|
}[];
|
||||||
|
target_kandangs: {
|
||||||
|
project_flock_kandang_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
}[];
|
}[];
|
||||||
reason: string;
|
reason: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user