mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
feat(FE-170,175): enhance RecordingForm and RecordingTable with approval logic and UI improvements
This commit is contained in:
@@ -51,6 +51,16 @@ const RowOptionsMenu = ({
|
||||
const isLayingCategory =
|
||||
props.row.original.project_flock_category === 'LAYING';
|
||||
|
||||
const isRecordingApproved = (recording: Recording) => {
|
||||
return (
|
||||
recording.approval?.action === 'APPROVED' &&
|
||||
recording.approval?.step_name === 'Disetujui' &&
|
||||
recording.approval?.step_number === 3
|
||||
);
|
||||
};
|
||||
|
||||
const isApproved = isRecordingApproved(props.row.original);
|
||||
|
||||
return (
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
@@ -82,24 +92,28 @@ const RowOptionsMenu = ({
|
||||
Grading
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={approveClickHandler}
|
||||
variant='ghost'
|
||||
color='success'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:check' width={16} height={16} />
|
||||
Approve
|
||||
</Button>
|
||||
<Button
|
||||
onClick={rejectClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:close' width={16} height={16} />
|
||||
Reject
|
||||
</Button>
|
||||
{!isApproved && (
|
||||
<Button
|
||||
onClick={approveClickHandler}
|
||||
variant='ghost'
|
||||
color='success'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:check' width={16} height={16} />
|
||||
Approve
|
||||
</Button>
|
||||
)}
|
||||
{!isApproved && (
|
||||
<Button
|
||||
onClick={rejectClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:close' width={16} height={16} />
|
||||
Reject
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
@@ -409,6 +423,14 @@ const RecordingTable = () => {
|
||||
isLoadingOptions: isLoadingKandang,
|
||||
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name');
|
||||
|
||||
const isRecordingApproved = useCallback((recording: Recording) => {
|
||||
return (
|
||||
recording.approval?.action === 'APPROVED' &&
|
||||
recording.approval?.step_name === 'Disetujui' &&
|
||||
recording.approval?.step_number === 3
|
||||
);
|
||||
}, []);
|
||||
|
||||
const searchChangeHandler = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateFilter('search', e.target.value);
|
||||
@@ -440,14 +462,22 @@ const RecordingTable = () => {
|
||||
const approveHandler = async () => {
|
||||
setIsApproveLoading(true);
|
||||
|
||||
if (eligibleRowIds.length === 0) {
|
||||
toast.error(
|
||||
'Tidak ada recording yang bisa disetujui (sudah disetujui sebelumnya)'
|
||||
);
|
||||
setIsApproveLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const approveResponse = await RecordingApi.approve(
|
||||
selectedRowIds,
|
||||
eligibleRowIds,
|
||||
approvalNotes
|
||||
);
|
||||
|
||||
if (isResponseSuccess(approveResponse)) {
|
||||
toast.success(
|
||||
`Berhasil approve ${selectedRowIds.length} data recording!`
|
||||
`Berhasil approve ${eligibleRowIds.length} data recording!`
|
||||
);
|
||||
approveModal.closeModal();
|
||||
refreshRecordings();
|
||||
@@ -465,13 +495,21 @@ const RecordingTable = () => {
|
||||
const rejectHandler = async () => {
|
||||
setIsRejectLoading(true);
|
||||
|
||||
if (eligibleRowIds.length === 0) {
|
||||
toast.error(
|
||||
'Tidak ada recording yang bisa ditolak (sudah disetujui sebelumnya)'
|
||||
);
|
||||
setIsRejectLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rejectResponse = await RecordingApi.reject(
|
||||
selectedRowIds,
|
||||
eligibleRowIds,
|
||||
approvalNotes
|
||||
);
|
||||
|
||||
if (isResponseSuccess(rejectResponse)) {
|
||||
toast.success(`Berhasil reject ${selectedRowIds.length} data recording!`);
|
||||
toast.success(`Berhasil reject ${eligibleRowIds.length} data recording!`);
|
||||
rejectModal.closeModal();
|
||||
refreshRecordings();
|
||||
setApprovalNotes('');
|
||||
@@ -485,6 +523,15 @@ const RecordingTable = () => {
|
||||
setIsRejectLoading(false);
|
||||
};
|
||||
|
||||
// Filter out already approved recordings for bulk actions
|
||||
const eligibleRowIds = useMemo(() => {
|
||||
if (!isResponseSuccess(recordings) || !recordings.data) return [];
|
||||
return selectedRowIds.filter((id) => {
|
||||
const recording = recordings.data.find((r) => r.id === id);
|
||||
return recording && !isRecordingApproved(recording);
|
||||
});
|
||||
}, [selectedRowIds, recordings, isRecordingApproved]);
|
||||
|
||||
return (
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
@@ -647,17 +694,21 @@ const RecordingTable = () => {
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div>
|
||||
<CheckboxInput
|
||||
name='row'
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect()}
|
||||
indeterminate={row.getIsSomeSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const isApproved = isRecordingApproved(row.original);
|
||||
return (
|
||||
<div>
|
||||
<CheckboxInput
|
||||
name='row'
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect() || isApproved}
|
||||
indeterminate={row.getIsSomeSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
title={isApproved ? 'Recording sudah disetujui' : ''}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: '#',
|
||||
@@ -893,7 +944,7 @@ const RecordingTable = () => {
|
||||
<ConfirmationModal
|
||||
ref={approveModal.ref}
|
||||
type='success'
|
||||
text={`Apakah anda yakin ingin approve data recording ini (${selectedRowIds.length} data)?`}
|
||||
text={`Apakah anda yakin ingin approve data recording ini (${eligibleRowIds.length} data dari ${selectedRowIds.length} yang dipilih)?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
onClick: () => setApprovalNotes(''),
|
||||
@@ -917,7 +968,7 @@ const RecordingTable = () => {
|
||||
<ConfirmationModal
|
||||
ref={rejectModal.ref}
|
||||
type='error'
|
||||
text={`Apakah anda yakin ingin reject data recording ini (${selectedRowIds.length} data)?`}
|
||||
text={`Apakah anda yakin ingin reject data recording ini (${eligibleRowIds.length} data dari ${selectedRowIds.length} yang dipilih)?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
onClick: () => setApprovalNotes(''),
|
||||
|
||||
@@ -2405,9 +2405,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||
{/* Left side - Detail & Edit actions */}
|
||||
{type !== 'add' && (
|
||||
<div className='flex flex-row justify-start gap-2'>
|
||||
{deleteRecordingClickHandler && (
|
||||
{type === 'detail' && deleteRecordingClickHandler && (
|
||||
<Button
|
||||
type='button'
|
||||
color='error'
|
||||
@@ -2423,7 +2424,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
{type !== 'edit' && initialValues && (
|
||||
{type === 'detail' && initialValues && (
|
||||
<Button
|
||||
type='button'
|
||||
color='warning'
|
||||
@@ -2442,11 +2443,50 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</div>
|
||||
)}
|
||||
<div className='flex flex-row justify-end gap-2'>
|
||||
{type !== 'detail' && (
|
||||
{type === 'detail' && isLayingCategory && (
|
||||
<Button
|
||||
type='button'
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/production/recording/grading/add?recording_id=${initialValues?.id}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Icon icon='material-symbols:egg' width={24} height={24} />
|
||||
Lanjut ke Grading
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{type === 'edit' && (
|
||||
<>
|
||||
<Button
|
||||
type='button'
|
||||
color='error'
|
||||
onClick={() => router.push('/production/recording')}
|
||||
className='px-4'
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
color='primary'
|
||||
className='px-4'
|
||||
isLoading={formik.isSubmitting}
|
||||
disabled={
|
||||
hasExceededStock || !formik.isValid || formik.isSubmitting
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === 'add' && (
|
||||
<>
|
||||
<Button
|
||||
type='reset'
|
||||
color='warning'
|
||||
color='neutral'
|
||||
className='px-4'
|
||||
onClick={(e) => {
|
||||
formik.handleReset(e);
|
||||
@@ -2489,7 +2529,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
);
|
||||
setTimeout(() => {
|
||||
router.push(
|
||||
'/production/recording/grading/add?recording_id=1'
|
||||
`/production/recording/grading/add?recording_id=${initialValues?.id || ''}`
|
||||
);
|
||||
}, 1000);
|
||||
}
|
||||
@@ -2505,21 +2545,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === 'detail' && isLayingCategory && (
|
||||
<Button
|
||||
type='button'
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/production/recording/grading/add?recording_id=${initialValues?.id}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Icon icon='material-symbols:egg' width={24} height={24} />
|
||||
Lanjut ke Grading
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{recordingFormErrorMessage && (
|
||||
|
||||
Reference in New Issue
Block a user