Merge branch 'dev/restu' into 'development'

[HOTFIX/FE][US#281-391] Adjustment Uniformity, Purchase and Expense

See merge request mbugroup/lti-web-client!138
This commit is contained in:
Rivaldi A N S
2026-01-07 02:15:53 +00:00
12 changed files with 115 additions and 62 deletions
+8
View File
@@ -33,6 +33,7 @@ const FileInput = ({
isError,
errorMessage,
disabled = false,
required = false,
onChange,
onBlur,
readOnly = false,
@@ -56,6 +57,13 @@ const FileInput = ({
)}
>
{label}
{required && (
<>
<span className='tooltip tooltip-error' data-tip='required'>
<span className='text-error'> *</span>
</span>
</>
)}
</label>
)}
@@ -140,17 +140,17 @@ const ExpenseRequestContent = ({
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
try {
await ExpenseApi.delete(initialValues?.id as number);
const deleteResponse = await ExpenseApi.delete(initialValues?.id as number);
if (isResponseSuccess(deleteResponse)) {
toast.success('Berhasil menghapus data biaya operasional!');
router.push('/expense');
} catch (error) {
} else {
toast.error('Gagal menghapus data biaya operasional!');
} finally {
deleteModal.closeModal();
setIsDeleteLoading(false);
}
deleteModal.closeModal();
setIsDeleteLoading(false);
};
const confirmationModalCompleteClickHandler = async () => {
@@ -21,7 +21,7 @@ const ExpenseStatusBadge = ({ approval }: ExpenseStatusBadgeProps) => {
switch (latestApprovalStepNumber) {
case 1:
expenseStatusPillBadgeColor = 'yellow';
expenseStatusPillBadgeColor = 'gray';
break;
case 2:
@@ -33,7 +33,7 @@ const ExpenseStatusBadge = ({ approval }: ExpenseStatusBadgeProps) => {
break;
case 4:
expenseStatusPillBadgeColor = 'red';
expenseStatusPillBadgeColor = 'yellow';
break;
case 5:
+12 -4
View File
@@ -420,11 +420,19 @@ const ExpensesTable = () => {
const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true);
await ExpenseApi.delete(selectedExpense?.id as number);
refreshExpenses();
const deleteResponse = await ExpenseApi.delete(
selectedExpense?.id as number
);
if (isResponseSuccess(deleteResponse)) {
refreshExpenses();
deleteModal.closeModal();
toast.success('Berhasil menghapus biaya operasional!');
} else {
deleteModal.closeModal();
toast.error('Gagal menghapus biaya operasional!');
}
deleteModal.closeModal();
toast.success('Berhasil menghapus biaya operasional!');
setIsDeleteLoading(false);
};
@@ -1562,7 +1562,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
width={20}
height={20}
/>
{delivery.document.name}
<span className='truncate max-w-[200px]'>
{delivery.document.name}
</span>
</Button>
) : (
<Button
@@ -1582,6 +1584,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
</>
) : (
<FileInput
accept='.pdf,.jpg,.jpeg,.png'
name={`deliveries.${idx}.document`}
onChange={(e) => {
const file = e.target.files?.[0];
@@ -51,8 +51,10 @@ import MenuItem from '@/components/menu/MenuItem';
const UniformityConfirmationPreview = ({
uniformity,
uniformityDetail,
}: {
uniformity?: Uniformity;
uniformityDetail?: UniformityDetail;
}) => {
const data: DetailOptionType[] = [
{
@@ -60,32 +62,42 @@ const UniformityConfirmationPreview = ({
label: 'Tanggal',
value: uniformity
? formatDate(uniformity.applied_at, 'DD MMM YYYY')
: '-',
: uniformityDetail
? formatDate(uniformityDetail.info_umum.tanggal, 'DD MMM YYYY')
: '-',
},
{
id: 'lokasi-farm',
label: 'Lokasi Farm',
value: uniformity?.location_name || '-',
value:
uniformity?.location_name ||
uniformityDetail?.info_umum?.lokasi_farm ||
'-',
},
{
id: 'project-flock',
label: 'Project Flock',
value: uniformity?.flock_name || '-',
value:
uniformity?.flock_name ||
uniformityDetail?.info_umum?.project_flock ||
'-',
},
{
id: 'kandang',
label: 'Kandang',
value: uniformity?.kandang_name || '-',
value:
uniformity?.kandang_name || uniformityDetail?.info_umum?.kandang || '-',
},
{
id: 'file-uniformity',
label: 'File Uniformity',
value: '-',
value:
uniformity?.file_name || uniformityDetail?.info_umum?.file_name || '-',
},
{
id: 'status',
label: 'Status',
value: uniformity?.status || '-',
value: uniformity?.status || (uniformityDetail ? 'CREATED' : '-'),
},
];
@@ -448,9 +460,15 @@ const UniformityTable = () => {
const canApproveReject = useMemo(() => {
return (
selectedUniformities.length > 0 &&
selectedUniformities.every(
(u) => u.status === 'CREATED' || u.status === 'Pengajuan'
)
selectedUniformities.every((u) => {
const approvalAction = u.latest_approval?.action;
return (
approvalAction === 'CREATED' ||
approvalAction === 'Pengajuan' ||
(!approvalAction &&
(u.status === 'CREATED' || u.status === 'Pengajuan'))
);
})
);
}, [selectedUniformities]);
@@ -805,7 +823,9 @@ const UniformityTable = () => {
accessorKey: 'status',
header: 'Status',
cell: (props) => {
const status = props.row.original.status;
const uniformity = props.row.original;
const status =
uniformity.latest_approval?.action ?? uniformity.status;
return (
<div className='w-full'>
<Badge
@@ -938,34 +958,7 @@ const UniformityTable = () => {
<div className='flex flex-col gap-4 mt-4'>
{createdUniformity ? (
<UniformityConfirmationPreview
uniformity={{
id: createdUniformity.id,
location_name: createdUniformity.info_umum.lokasi_farm,
flock_name: createdUniformity.info_umum.project_flock,
kandang_name: createdUniformity.info_umum.kandang,
applied_at: createdUniformity.info_umum.tanggal,
week: 0,
status: 'Pengajuan',
uniformity: createdUniformity.result.uniformity,
cv: createdUniformity.result.cv,
chick_qty_of_weight:
createdUniformity.sampling.chick_qty_of_weight,
uniform_qty: createdUniformity.result.uniform_qty,
mean_up: createdUniformity.sampling.mean_up,
mean_down: createdUniformity.sampling.mean_down,
standard_mean_weight: null,
standard_uniformity: null,
created_at: '',
created_by: 0,
project_flock_kandang_id: 0,
created_user: {
id: 0,
id_user: 0,
email: '',
name: '',
},
updated_at: '',
}}
uniformityDetail={createdUniformity}
/>
) : selectedRowIds.length === 1 ? (
<UniformityConfirmationPreview
@@ -689,6 +689,16 @@ const PurchaseOrderAcceptApprovalForm = ({
accept='.pdf,.jpg,.jpeg,.png'
onChange={(e) => {
const files = Array.from(e.target.files || []);
const invalidFiles = files.filter(
(file) => file.size > 2 * 1024 * 1024
);
if (invalidFiles.length > 0) {
toast.error('Ukuran dokumen maksimal 2 MB!');
e.target.value = '';
return;
}
formik.setFieldValue('travel_documents', files);
}}
onBlur={formik.handleBlur}
@@ -312,7 +312,8 @@ export const PurchaseRequestStaffApprovalFormInitialValues: PurchaseRequestStaff
};
export const PurchaseRequestStaffApprovalFormDefaultValues = (
purchase?: Purchase
purchase?: Purchase,
type?: 'add' | 'edit'
): PurchaseRequestStaffApprovalFormSchemaType => {
return {
action: 'APPROVED',
@@ -331,8 +332,18 @@ export const PurchaseRequestStaffApprovalFormDefaultValues = (
label: item.warehouse?.name || '',
},
qty: item.sub_qty || item.qty || 0,
price: item.price,
total_price: item.total_price,
price:
type === 'add'
? 'ProductPrice' in item.product
? item.product.ProductPrice || item.price || ''
: item.price
: item.price,
total_price:
type === 'add'
? ('ProductPrice' in item.product
? item.product.ProductPrice || item.price || 0
: item.price) * (item.sub_qty || item.qty || 0)
: item.total_price,
}))
: [
{
@@ -381,7 +392,15 @@ export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema<PurchaseR
.required('Item pembelian wajib diisi!')
.typeError('Item pembelian wajib diisi!'),
travel_documents: Yup.array()
.of(Yup.mixed<File>().required())
.of(
Yup.mixed<File>()
.required('Dokumen surat jalan wajib diupload!')
.test('fileSize', 'Ukuran dokumen maksimal 2 MB', (value) => {
if (!value) return true;
if (value instanceof File) return value.size <= 2 * 1024 * 1024;
return true;
})
)
.required('Dokumen surat jalan wajib diupload!')
.min(1, 'Minimal upload 1 dokumen surat jalan!')
.typeError('Dokumen surat jalan wajib diupload!'),
@@ -294,9 +294,9 @@ const PurchaseOrderStaffApprovalForm = ({
// ===== FORM CONFIGURATION =====
const formikInitialValues = useMemo(() => {
return initialValues
? PurchaseRequestStaffApprovalFormDefaultValues(initialValues)
? PurchaseRequestStaffApprovalFormDefaultValues(initialValues, type)
: PurchaseRequestStaffApprovalFormInitialValues;
}, [initialValues]);
}, [initialValues, type]);
const formik = useFormik({
initialValues: formikInitialValues,
@@ -485,9 +485,18 @@ const PurchaseOrderStaffApprovalForm = ({
},
warehouse_id: purchaseItem.warehouse_id || 0,
qty: originalItem?.qty || purchaseItem.quantity || 0,
price: type === 'edit' && originalItem ? originalItem.price : '',
price:
type === 'edit' && originalItem
? originalItem.price
: originalItem?.product && 'ProductPrice' in originalItem.product
? originalItem.product.ProductPrice || ''
: '',
total_price:
type === 'edit' && originalItem ? originalItem.total_price : '',
type === 'edit' && originalItem
? originalItem.total_price
: (originalItem?.product && 'ProductPrice' in originalItem.product
? originalItem.product.ProductPrice || 0
: 0) * (originalItem?.qty || purchaseItem.quantity || 0),
};
return itemData;
});
@@ -1140,6 +1149,7 @@ const PurchaseOrderStaffApprovalForm = ({
color='warning'
className='px-4'
onClick={() => {
formik.setValues(formikInitialValues);
formik.resetForm();
setPurchaseOrderFormErrorMessage('');
onCancel?.();
+2
View File
@@ -10,6 +10,8 @@ export type BaseInventoryProduct = {
name: string;
brand: string;
sku: string;
ProductPrice: number;
SellingPrice?: number;
product_price: number;
selling_price?: number;
tax?: number;
+1 -3
View File
@@ -1,7 +1,4 @@
import { BaseMetadata } from '@/types/api/api-general';
import { Location } from '@/types/api/location/location';
import { ProjectFlock } from '@/types/api/project-flock/project-flock';
import { Kandang } from '@/types/api/kandang/kandang';
import { BaseApproval } from '@/types/api/approval/approval';
// ==================== GET ALL RESPONSE ====================
@@ -11,6 +8,7 @@ export type Uniformity = BaseMetadata & {
location_name: string;
flock_name: string;
kandang_name: string;
file_name: string;
applied_at: string;
week: number;
status: string;
+2
View File
@@ -10,6 +10,8 @@ export type PurchaseItemProduct = {
id: number;
name: string;
flags?: string[];
ProductPrice?: number;
SellingPrice?: number;
uom?: {
name: string;
};