mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 07:15:44 +00:00
Merge branch 'fix/adjustment-penjualan' into 'development'
[FIX/FE] Adjustment Penjualan UI and Data Fetch See merge request mbugroup/lti-web-client!317
This commit is contained in:
@@ -276,6 +276,13 @@ const ExpensesTable = () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'reference_number',
|
||||||
|
header: 'Nomor Referensi',
|
||||||
|
cell: (props) => {
|
||||||
|
return props.row.original.reference_number ?? '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'transaction_date',
|
accessorKey: 'transaction_date',
|
||||||
header: 'Tanggal Pengajuan',
|
header: 'Tanggal Pengajuan',
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ const DeliveryOrderFormModal = ({
|
|||||||
const modalAction = searchParams.get('action');
|
const modalAction = searchParams.get('action');
|
||||||
const marketingId = searchParams.get('id');
|
const marketingId = searchParams.get('id');
|
||||||
|
|
||||||
|
const [currentModalAction, setCurrentModalAction] = useState<string | null>(
|
||||||
|
modalAction
|
||||||
|
);
|
||||||
|
|
||||||
const isModalActionForForm =
|
const isModalActionForForm =
|
||||||
modalAction === 'add_delivery' ||
|
modalAction === 'add_delivery' ||
|
||||||
modalAction === 'edit_delivery' ||
|
modalAction === 'edit_delivery' ||
|
||||||
@@ -420,17 +424,7 @@ const DeliveryOrderFormModal = ({
|
|||||||
const deliveryRejected = useMemo(() => {
|
const deliveryRejected = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
isResponseSuccess(marketing) &&
|
isResponseSuccess(marketing) &&
|
||||||
((marketing.data.latest_approval.step_number === 3 &&
|
marketing.data.latest_approval.action === 'REJECTED'
|
||||||
marketing.data.latest_approval.action === 'REJECTED') ||
|
|
||||||
(marketing.data.latest_approval.step_number === 2 &&
|
|
||||||
marketing.data.latest_approval.action === 'REJECTED'))
|
|
||||||
);
|
|
||||||
}, [marketing]);
|
|
||||||
|
|
||||||
const isPending = useMemo(() => {
|
|
||||||
return (
|
|
||||||
isResponseSuccess(marketing) &&
|
|
||||||
marketing.data.latest_approval.step_number === 1
|
|
||||||
);
|
);
|
||||||
}, [marketing]);
|
}, [marketing]);
|
||||||
|
|
||||||
@@ -441,6 +435,7 @@ const DeliveryOrderFormModal = ({
|
|||||||
modalAction === 'edit_delivery' ||
|
modalAction === 'edit_delivery' ||
|
||||||
modalAction === 'detail'
|
modalAction === 'detail'
|
||||||
) {
|
) {
|
||||||
|
setCurrentModalAction(modalAction);
|
||||||
formModal.openModal();
|
formModal.openModal();
|
||||||
}
|
}
|
||||||
}, [modalAction]);
|
}, [modalAction]);
|
||||||
@@ -562,9 +557,11 @@ const DeliveryOrderFormModal = ({
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>No. Sales Order</td>
|
<td className='text-sm px-4 py-3'>No. Order</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{marketing.data.so_number}
|
{marketing.data.do_number
|
||||||
|
? marketing.data.do_number
|
||||||
|
: marketing.data.so_number}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -667,13 +664,7 @@ const DeliveryOrderFormModal = ({
|
|||||||
<div className='px-4'>
|
<div className='px-4'>
|
||||||
<MemoizedDeliveryOrderProductTable
|
<MemoizedDeliveryOrderProductTable
|
||||||
marketing={marketing.data}
|
marketing={marketing.data}
|
||||||
formType={
|
formType={deliveryRejected ? 'rejected' : modalAction}
|
||||||
deliveryRejected
|
|
||||||
? 'rejected'
|
|
||||||
: isPending
|
|
||||||
? 'pending'
|
|
||||||
: modalAction
|
|
||||||
}
|
|
||||||
data={deliveryOrderValues}
|
data={deliveryOrderValues}
|
||||||
onEdit={handleEditDO}
|
onEdit={handleEditDO}
|
||||||
onDelete={handleDeleteDO}
|
onDelete={handleDeleteDO}
|
||||||
@@ -715,31 +706,32 @@ const DeliveryOrderFormModal = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{step === 1 && (
|
{step === 1 &&
|
||||||
<div className='w-full px-4 py-3 grid grid-cols-2 items-center justify-between gap-3 border-t border-base-content/10'>
|
marketing?.data?.latest_approval?.step_number !== 3 && (
|
||||||
<Button
|
<div className='w-full px-4 py-3 grid grid-cols-2 items-center justify-between gap-3 border-t border-base-content/10'>
|
||||||
type='button'
|
<Button
|
||||||
variant='outline'
|
type='button'
|
||||||
color='none'
|
variant='outline'
|
||||||
onClick={() => rejectModal.openModal()}
|
color='none'
|
||||||
disabled={deliveryRejected || isPending}
|
onClick={() => rejectModal.openModal()}
|
||||||
className='p-3 border-base-content/10 shadow-button-soft rounded-lg text-sm text-base-content/50 font-semibold'
|
disabled={deliveryRejected}
|
||||||
>
|
className='p-3 border-base-content/10 shadow-button-soft rounded-lg text-sm text-base-content/50 font-semibold'
|
||||||
Reject
|
>
|
||||||
</Button>
|
Reject
|
||||||
<Button
|
</Button>
|
||||||
type='button'
|
<Button
|
||||||
color='primary'
|
type='button'
|
||||||
onClick={() => {
|
color='primary'
|
||||||
formRef.current?.requestSubmit();
|
onClick={() => {
|
||||||
}}
|
formRef.current?.requestSubmit();
|
||||||
className='p-3 shadow-button-soft text-base-100 rounded-lg text-sm font-semibold'
|
}}
|
||||||
disabled={deliveryRejected || isPending}
|
className='p-3 shadow-button-soft text-base-100 rounded-lg text-sm font-semibold'
|
||||||
>
|
disabled={deliveryRejected}
|
||||||
Approve
|
>
|
||||||
</Button>
|
Approve
|
||||||
</div>
|
</Button>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -749,8 +741,8 @@ const DeliveryOrderFormModal = ({
|
|||||||
ref={successModal.ref}
|
ref={successModal.ref}
|
||||||
iconPosition='left'
|
iconPosition='left'
|
||||||
type='success'
|
type='success'
|
||||||
text={`${modalAction === 'add' ? 'Data Berhasil Disimpan' : 'Data Berhasil Diubah'}`}
|
text={`${currentModalAction === 'add' ? 'Data Berhasil Disimpan' : 'Data Berhasil Diubah'}`}
|
||||||
subtitleText={`${modalAction === 'add' ? 'Data delivery order telah berhasil disimpan.' : 'Data delivery order telah berhasil diubah.'}`}
|
subtitleText={`${currentModalAction === 'add' ? 'Data delivery order telah berhasil disimpan.' : 'Data delivery order telah berhasil diubah.'}`}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: 'Oke',
|
text: 'Oke',
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
@@ -760,14 +752,18 @@ const DeliveryOrderFormModal = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MemoizedDeliveryOrderProductTable
|
<div className='max-h-[50vh] overflow-y-auto'>
|
||||||
marketing={isResponseSuccess(marketing) ? marketing.data : undefined}
|
<MemoizedDeliveryOrderProductTable
|
||||||
formType={'success'}
|
marketing={
|
||||||
data={deliveryOrderValues}
|
isResponseSuccess(marketing) ? marketing.data : undefined
|
||||||
onDelete={handleDeleteDO}
|
}
|
||||||
onEdit={handleEditDO}
|
formType={'success'}
|
||||||
onAddProductClick={handleAddDOClick}
|
data={deliveryOrderValues}
|
||||||
/>
|
onDelete={handleDeleteDO}
|
||||||
|
onEdit={handleEditDO}
|
||||||
|
onAddProductClick={handleAddDOClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
||||||
<ConfirmationModalWithNotes
|
<ConfirmationModalWithNotes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { RefObject } from 'react';
|
import { RefObject, useMemo } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
@@ -9,10 +9,12 @@ import SelectInput, {
|
|||||||
OptionType,
|
OptionType,
|
||||||
useSelect,
|
useSelect,
|
||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
import { CustomerApi, ProductApi } from '@/services/api/master-data';
|
|
||||||
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
|
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
import { MarketingFilter } from '@/types/api/marketing/marketing';
|
import { MarketingFilter } from '@/types/api/marketing/marketing';
|
||||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
|
import { MarketingApi } from '@/services/api/marketing/marketing';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { BaseMarketing, BaseSalesOrder } from '@/types/api/marketing/marketing';
|
||||||
|
|
||||||
interface MarketingFilterModal {
|
interface MarketingFilterModal {
|
||||||
ref: RefObject<HTMLDialogElement | null>;
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
@@ -31,25 +33,59 @@ const MarketingFilterModal = ({
|
|||||||
|
|
||||||
// ===== OPTIONS =====
|
// ===== OPTIONS =====
|
||||||
const {
|
const {
|
||||||
options: productsOptions,
|
rawData: productsRawData,
|
||||||
isLoadingOptions: isLoadingProductsOptions,
|
isLoadingOptions: isLoadingProductsOptions,
|
||||||
setInputValue: setProductsInputValue,
|
setInputValue: setProductsInputValue,
|
||||||
loadMore: loadMoreProducts,
|
loadMore: loadMoreProducts,
|
||||||
} = useSelect(ProductApi.basePath, 'id', 'name', '', {
|
} = useSelect<BaseMarketing>(MarketingApi.basePath, 'id', 'so_number', '', {
|
||||||
limit: 'limit',
|
limit: 'limit',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const productsOptions = useMemo(() => {
|
||||||
|
if (!productsRawData || !isResponseSuccess(productsRawData)) return [];
|
||||||
|
|
||||||
|
const productsMap = new Map<number, { value: number; label: string }>();
|
||||||
|
|
||||||
|
productsRawData.data.forEach((deliveryOrder: BaseMarketing) => {
|
||||||
|
deliveryOrder.sales_order?.forEach((so: BaseSalesOrder) => {
|
||||||
|
const product = so.product_warehouse?.product;
|
||||||
|
if (product?.id && product?.name) {
|
||||||
|
productsMap.set(product.id, {
|
||||||
|
value: product.id,
|
||||||
|
label: product.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(productsMap.values());
|
||||||
|
}, [productsRawData]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: customersOptions,
|
options: customersOptions,
|
||||||
isLoadingOptions: isLoadingCustomersOptions,
|
isLoadingOptions: isLoadingCustomersOptions,
|
||||||
setInputValue: setCustomersInputValue,
|
setInputValue: setCustomersInputValue,
|
||||||
loadMore: loadMoreCustomers,
|
loadMore: loadMoreCustomers,
|
||||||
} = useSelect(CustomerApi.basePath, 'id', 'name', '', {
|
} = useSelect(MarketingApi.basePath, 'customer.id', 'customer.name', '', {
|
||||||
limit: 'limit',
|
limit: 'limit',
|
||||||
});
|
});
|
||||||
const statusOptions = MARKETING_APPROVAL_LINE.map((item) => ({
|
|
||||||
value: item.step_name.split(' ').join('_').toUpperCase(),
|
const uniqueCustomersOptions = useMemo(() => {
|
||||||
label: item.step_name,
|
const seen = new Set();
|
||||||
}));
|
return customersOptions.filter((customer) => {
|
||||||
|
if (seen.has(customer.value)) return false;
|
||||||
|
seen.add(customer.value);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [customersOptions]);
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
...MARKETING_APPROVAL_LINE.map((item) => ({
|
||||||
|
value: item.step_name.split(' ').join('_').toUpperCase(),
|
||||||
|
label: item.step_name,
|
||||||
|
})),
|
||||||
|
{ value: 'DITOLAK', label: 'Ditolak' },
|
||||||
|
];
|
||||||
|
|
||||||
const formik = useFormik<{
|
const formik = useFormik<{
|
||||||
product_ids: OptionType[];
|
product_ids: OptionType[];
|
||||||
@@ -151,7 +187,7 @@ const MarketingFilterModal = ({
|
|||||||
label='Customer'
|
label='Customer'
|
||||||
isClearable
|
isClearable
|
||||||
placeholder='Pilih customer'
|
placeholder='Pilih customer'
|
||||||
options={customersOptions}
|
options={uniqueCustomersOptions}
|
||||||
isLoading={isLoadingCustomersOptions}
|
isLoading={isLoadingCustomersOptions}
|
||||||
value={formik.values.customer_id}
|
value={formik.values.customer_id}
|
||||||
onChange={customerChangeHandler}
|
onChange={customerChangeHandler}
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ const RowsOptionsMenu = ({
|
|||||||
className='p-3 justify-start text-sm font-semibold w-full'
|
className='p-3 justify-start text-sm font-semibold w-full'
|
||||||
>
|
>
|
||||||
<Icon icon='heroicons:truck' width={20} height={20} />
|
<Icon icon='heroicons:truck' width={20} height={20} />
|
||||||
Deliver Item
|
{props.row.original.latest_approval.step_number == 2
|
||||||
|
? 'Deliver Item'
|
||||||
|
: 'Edit Delivery'}
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
</>
|
</>
|
||||||
@@ -379,8 +381,13 @@ const MarketingTable = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'so_number',
|
accessorKey: 'so_do_number',
|
||||||
header: 'No. Order',
|
header: 'No. Order',
|
||||||
|
cell: (props) => {
|
||||||
|
return props.row.original.do_number
|
||||||
|
? props.row.original.do_number
|
||||||
|
: props.row.original.so_number;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'so_date',
|
accessorKey: 'so_date',
|
||||||
@@ -408,7 +415,7 @@ const MarketingTable = () => {
|
|||||||
: approval?.step_number == 2
|
: approval?.step_number == 2
|
||||||
? 'info'
|
? 'info'
|
||||||
: approval?.step_number == 3
|
: approval?.step_number == 3
|
||||||
? 'warning'
|
? 'success'
|
||||||
: 'neutral'
|
: 'neutral'
|
||||||
: 'neutral'
|
: 'neutral'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ const SalesOrderFormModal = ({
|
|||||||
const modalAction = searchParams.get('action');
|
const modalAction = searchParams.get('action');
|
||||||
const marketingId = searchParams.get('id');
|
const marketingId = searchParams.get('id');
|
||||||
|
|
||||||
|
const [currentModalAction, setCurrentModalAction] = useState<string | null>(
|
||||||
|
modalAction
|
||||||
|
);
|
||||||
|
|
||||||
const isModalActionForForm =
|
const isModalActionForForm =
|
||||||
modalAction === 'add' ||
|
modalAction === 'add' ||
|
||||||
modalAction === 'edit' ||
|
modalAction === 'edit' ||
|
||||||
@@ -208,7 +212,7 @@ const SalesOrderFormModal = ({
|
|||||||
convertion_unit: normalizedConvertionUnit,
|
convertion_unit: normalizedConvertionUnit,
|
||||||
weight_per_convertion:
|
weight_per_convertion:
|
||||||
product.weight_per_convertion ?? undefined,
|
product.weight_per_convertion ?? undefined,
|
||||||
week: product.week?.value ?? undefined,
|
week: product.week ?? undefined,
|
||||||
} as CreateSalesOrderProductPayload;
|
} as CreateSalesOrderProductPayload;
|
||||||
}),
|
}),
|
||||||
} as CreateSalesOrderPayload)
|
} as CreateSalesOrderPayload)
|
||||||
@@ -412,6 +416,7 @@ const SalesOrderFormModal = ({
|
|||||||
// ================== EFFECT ==================
|
// ================== EFFECT ==================
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalAction === 'add' || modalAction === 'edit') {
|
if (modalAction === 'add' || modalAction === 'edit') {
|
||||||
|
setCurrentModalAction(modalAction);
|
||||||
formModal.openModal();
|
formModal.openModal();
|
||||||
}
|
}
|
||||||
}, [modalAction]);
|
}, [modalAction]);
|
||||||
@@ -724,8 +729,8 @@ const SalesOrderFormModal = ({
|
|||||||
ref={successModal.ref}
|
ref={successModal.ref}
|
||||||
iconPosition='left'
|
iconPosition='left'
|
||||||
type='success'
|
type='success'
|
||||||
text={`${modalAction === 'add' ? 'Data Berhasil Ditambahkan' : 'Data Berhasil Diubah'}`}
|
text={`${currentModalAction === 'add' ? 'Data Berhasil Ditambahkan' : 'Data Berhasil Diubah'}`}
|
||||||
subtitleText={`${modalAction === 'add' ? 'Data sales order telah berhasil disimpan.' : 'Data sales order telah berhasil diubah.'}`}
|
subtitleText={`${currentModalAction === 'add' ? 'Data sales order telah berhasil disimpan.' : 'Data sales order telah berhasil diubah.'}`}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: 'Oke',
|
text: 'Oke',
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
@@ -735,13 +740,15 @@ const SalesOrderFormModal = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MemoizedSalesOrderProductTable
|
<div className='max-h-[50vh] overflow-y-auto'>
|
||||||
formType={'success'}
|
<MemoizedSalesOrderProductTable
|
||||||
data={memoSalesOrder}
|
formType={'success'}
|
||||||
onDelete={handleDeleteSO}
|
data={memoSalesOrder}
|
||||||
onEdit={handleEditSO}
|
onDelete={handleDeleteSO}
|
||||||
onAddProductClick={handleAddSOClick}
|
onEdit={handleEditSO}
|
||||||
/>
|
onAddProductClick={handleAddSOClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
|||||||
@@ -128,12 +128,7 @@ export const SalesProductToFieldValues = (
|
|||||||
label: formatTitleCase(product.convertion_unit),
|
label: formatTitleCase(product.convertion_unit),
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
week: product.week
|
week: product.week ?? null,
|
||||||
? {
|
|
||||||
value: product.week,
|
|
||||||
label: `Week ${product.week}`,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
total_peti: product.total_peti,
|
total_peti: product.total_peti,
|
||||||
weight_per_convertion: product.weight_per_convertion,
|
weight_per_convertion: product.weight_per_convertion,
|
||||||
uom: product.product_warehouse.product.uom.name,
|
uom: product.product_warehouse.product.uom.name,
|
||||||
|
|||||||
+7
-21
@@ -30,13 +30,7 @@ type DeliveryOrderProductSchemaType = {
|
|||||||
/** Harga per butir telur untuk TELUR + QTY */
|
/** Harga per butir telur untuk TELUR + QTY */
|
||||||
price_per_qty?: number | null | undefined;
|
price_per_qty?: number | null | undefined;
|
||||||
/** Week untuk ayam pullet */
|
/** Week untuk ayam pullet */
|
||||||
week?:
|
week?: number | null | undefined;
|
||||||
| {
|
|
||||||
value?: number;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
| null
|
|
||||||
| undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeliveryOrderProductSchema: Yup.ObjectSchema<DeliveryOrderProductSchemaType> =
|
export const DeliveryOrderProductSchema: Yup.ObjectSchema<DeliveryOrderProductSchemaType> =
|
||||||
@@ -79,26 +73,18 @@ export const DeliveryOrderProductSchema: Yup.ObjectSchema<DeliveryOrderProductSc
|
|||||||
sisa_berat: Yup.number().nullable().optional().notRequired(),
|
sisa_berat: Yup.number().nullable().optional().notRequired(),
|
||||||
price_sisa_berat: Yup.number().nullable().optional().notRequired(),
|
price_sisa_berat: Yup.number().nullable().optional().notRequired(),
|
||||||
price_per_qty: Yup.number().nullable().optional().notRequired(),
|
price_per_qty: Yup.number().nullable().optional().notRequired(),
|
||||||
week: Yup.object({
|
week: Yup.number()
|
||||||
value: Yup.number(),
|
|
||||||
label: Yup.string(),
|
|
||||||
})
|
|
||||||
.nullable()
|
.nullable()
|
||||||
.default(null)
|
.optional()
|
||||||
|
.notRequired()
|
||||||
.when('marketing_type', {
|
.when('marketing_type', {
|
||||||
is: (marketingType: { value: string } | null | undefined) =>
|
is: (marketingType: { value: string } | null | undefined) =>
|
||||||
marketingType?.value?.toLowerCase() === 'ayam_pullet',
|
marketingType?.value?.toLowerCase() === 'ayam_pullet',
|
||||||
then: (schema) =>
|
then: (schema) =>
|
||||||
schema
|
schema
|
||||||
.shape({
|
.min(1, 'Week wajib diisi untuk Ayam Pullet!')
|
||||||
value: Yup.number().required(
|
.required('Week wajib diisi untuk Ayam Pullet!')
|
||||||
'Week wajib diisi untuk Ayam Pullet!'
|
.typeError('Week harus berupa angka!'),
|
||||||
),
|
|
||||||
label: Yup.string().required(
|
|
||||||
'Week wajib diisi untuk Ayam Pullet!'
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.required('Week wajib diisi untuk Ayam Pullet!'),
|
|
||||||
otherwise: (schema) => schema.optional().notRequired(),
|
otherwise: (schema) => schema.optional().notRequired(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-10
@@ -511,19 +511,20 @@ const DeliveryOrderProductForm = ({
|
|||||||
{/* Konversi Satuan Week Pullet */}
|
{/* Konversi Satuan Week Pullet */}
|
||||||
{formik.values.marketing_type?.value.toLowerCase() ===
|
{formik.values.marketing_type?.value.toLowerCase() ===
|
||||||
'ayam_pullet' && (
|
'ayam_pullet' && (
|
||||||
<SelectInputRadio
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Minggu'
|
label='Minggu'
|
||||||
options={optionsWeek}
|
name='week'
|
||||||
value={
|
value={formik.values.week ?? undefined}
|
||||||
formik.values.week?.value
|
onChange={(e) => {
|
||||||
? (formik.values.week as { value: number; label: string })
|
formik.setFieldValue('week', Number(e.target.value));
|
||||||
: null
|
setCurrentInput(e.target.name);
|
||||||
}
|
|
||||||
onChange={(val) => {
|
|
||||||
formik.setFieldValue('week', val);
|
|
||||||
}}
|
}}
|
||||||
placeholder='Pilih Week'
|
onBlur={() => handleBlurField('week')}
|
||||||
|
isError={formik.touched.week && Boolean(formik.errors.week)}
|
||||||
|
errorMessage={formik.errors.week as string}
|
||||||
|
placeholder='Masukan Minggu'
|
||||||
|
decimalScale={0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
+7
-21
@@ -37,13 +37,7 @@ type SalesOrderProductSchemaType = {
|
|||||||
/** Harga per butir telur untuk TELUR + QTY */
|
/** Harga per butir telur untuk TELUR + QTY */
|
||||||
price_per_qty?: number | null | undefined;
|
price_per_qty?: number | null | undefined;
|
||||||
/** Week untuk ayam pullet */
|
/** Week untuk ayam pullet */
|
||||||
week?:
|
week?: number | null | undefined;
|
||||||
| {
|
|
||||||
value?: number;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
| null
|
|
||||||
| undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaType> =
|
export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaType> =
|
||||||
@@ -102,26 +96,18 @@ export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaTy
|
|||||||
sisa_berat: Yup.number().nullable().optional().notRequired(),
|
sisa_berat: Yup.number().nullable().optional().notRequired(),
|
||||||
price_sisa_berat: Yup.number().nullable().optional().notRequired(),
|
price_sisa_berat: Yup.number().nullable().optional().notRequired(),
|
||||||
price_per_qty: Yup.number().nullable().optional().notRequired(),
|
price_per_qty: Yup.number().nullable().optional().notRequired(),
|
||||||
week: Yup.object({
|
week: Yup.number()
|
||||||
value: Yup.number(),
|
|
||||||
label: Yup.string(),
|
|
||||||
})
|
|
||||||
.nullable()
|
.nullable()
|
||||||
.default(null)
|
.optional()
|
||||||
|
.notRequired()
|
||||||
.when('marketing_type', {
|
.when('marketing_type', {
|
||||||
is: (marketingType: { value: string } | null | undefined) =>
|
is: (marketingType: { value: string } | null | undefined) =>
|
||||||
marketingType?.value?.toLowerCase() === 'ayam_pullet',
|
marketingType?.value?.toLowerCase() === 'ayam_pullet',
|
||||||
then: (schema) =>
|
then: (schema) =>
|
||||||
schema
|
schema
|
||||||
.shape({
|
.min(1, 'Week wajib diisi untuk Ayam Pullet!')
|
||||||
value: Yup.number().required(
|
.required('Week wajib diisi untuk Ayam Pullet!')
|
||||||
'Week wajib diisi untuk Ayam Pullet!'
|
.typeError('Week harus berupa angka!'),
|
||||||
),
|
|
||||||
label: Yup.string().required(
|
|
||||||
'Week wajib diisi untuk Ayam Pullet!'
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.required('Week wajib diisi untuk Ayam Pullet!'),
|
|
||||||
otherwise: (schema) => schema.optional().notRequired(),
|
otherwise: (schema) => schema.optional().notRequired(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-10
@@ -467,19 +467,20 @@ const SalesOrderProductForm = ({
|
|||||||
{/* Konversi Satuan Week Pullet */}
|
{/* Konversi Satuan Week Pullet */}
|
||||||
{formik.values.marketing_type?.value.toLowerCase() ===
|
{formik.values.marketing_type?.value.toLowerCase() ===
|
||||||
'ayam_pullet' && (
|
'ayam_pullet' && (
|
||||||
<SelectInputRadio
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Minggu'
|
label='Minggu'
|
||||||
options={optionsWeek}
|
name='week'
|
||||||
value={
|
value={formik.values.week ?? undefined}
|
||||||
formik.values.week?.value
|
onChange={(e) => {
|
||||||
? (formik.values.week as { value: number; label: string })
|
formik.setFieldValue('week', Number(e.target.value));
|
||||||
: null
|
setCurrentInput(e.target.name);
|
||||||
}
|
|
||||||
onChange={(val) => {
|
|
||||||
formik.setFieldValue('week', val);
|
|
||||||
}}
|
}}
|
||||||
placeholder='Pilih Week'
|
onBlur={() => handleBlurField('week')}
|
||||||
|
isError={formik.touched.week && Boolean(formik.errors.week)}
|
||||||
|
errorMessage={formik.errors.week as string}
|
||||||
|
placeholder='Masukan Minggu'
|
||||||
|
decimalScale={0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
@@ -39,6 +40,8 @@ const DeliveryOrderProductTable = ({
|
|||||||
const onDeleteRef = useRef(onDelete);
|
const onDeleteRef = useRef(onDelete);
|
||||||
onDeleteRef.current = onDelete;
|
onDeleteRef.current = onDelete;
|
||||||
|
|
||||||
|
const approvalStepNumber = marketing?.latest_approval?.step_number;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||||
@@ -47,9 +50,20 @@ const DeliveryOrderProductTable = ({
|
|||||||
(doItem) => doItem.do_number === item.do_number
|
(doItem) => doItem.do_number === item.do_number
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div
|
<Card
|
||||||
className='rounded-lg border border-tools-table-outline border-base-content/5'
|
|
||||||
key={`table-${item.id}`}
|
key={`table-${item.id}`}
|
||||||
|
title={
|
||||||
|
item.marketing_product?.product_warehouse?.label || 'Produk'
|
||||||
|
}
|
||||||
|
collapsible={true}
|
||||||
|
defaultCollapsed={false}
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full rounded-lg',
|
||||||
|
body: 'p-0',
|
||||||
|
title: 'px-2 py-1.5 font-normal text-sm',
|
||||||
|
collapsible: 'rounded-lg',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<table
|
<table
|
||||||
style={{
|
style={{
|
||||||
@@ -58,12 +72,12 @@ const DeliveryOrderProductTable = ({
|
|||||||
className='border-none w-full'
|
className='border-none w-full'
|
||||||
>
|
>
|
||||||
<tbody className='w-full'>
|
<tbody className='w-full'>
|
||||||
<tr className='border-b border-tools-table-outline border-base-content/5'>
|
<tr className='border-b border-t border-tools-table-outline border-base-content/5'>
|
||||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||||
Label
|
Label
|
||||||
</th>
|
</th>
|
||||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full mt-2'>
|
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||||
<div>Value</div>
|
<div>Value</div>
|
||||||
{(formType === 'add_delivery' ||
|
{(formType === 'add_delivery' ||
|
||||||
formType === 'edit_delivery' ||
|
formType === 'edit_delivery' ||
|
||||||
@@ -105,16 +119,20 @@ const DeliveryOrderProductTable = ({
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<>
|
<>
|
||||||
<tr>
|
{approvalStepNumber !== 1 && (
|
||||||
<td className='text-sm px-4 py-3'>Tanggal Pengiriman</td>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{item.delivery_date ? (
|
Tanggal Pengiriman
|
||||||
formatDate(item.delivery_date, 'DD MMM YYYY')
|
</td>
|
||||||
) : (
|
<td className='text-sm px-4 py-3'>
|
||||||
<span className='text-error'>Belum diisi</span>
|
{item.delivery_date ? (
|
||||||
)}
|
formatDate(item.delivery_date, 'DD MMM YYYY')
|
||||||
</td>
|
) : (
|
||||||
</tr>
|
<span className='text-error'>Belum diisi</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
{item.do_number && (
|
{item.do_number && (
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>No. Pengiriman</td>
|
<td className='text-sm px-4 py-3'>No. Pengiriman</td>
|
||||||
@@ -130,7 +148,9 @@ const DeliveryOrderProductTable = ({
|
|||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{item.marketing_product?.product_warehouse?.label}
|
{doItem?.warehouse?.name ||
|
||||||
|
item.marketing_product?.product_warehouse_data
|
||||||
|
?.warehouse?.name}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -191,7 +211,7 @@ const DeliveryOrderProductTable = ({
|
|||||||
</>
|
</>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||||
import {
|
import {
|
||||||
formatCurrency,
|
formatCurrency,
|
||||||
@@ -146,9 +147,18 @@ const SalesOrderProductTable = ({
|
|||||||
<>
|
<>
|
||||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||||
{data.map((item) => (
|
{data.map((item) => (
|
||||||
<div
|
<Card
|
||||||
className='rounded-lg border border-tools-table-outline border-base-content/5'
|
|
||||||
key={`table-${item.id}`}
|
key={`table-${item.id}`}
|
||||||
|
title={item.product_warehouse?.label || 'Produk'}
|
||||||
|
collapsible={true}
|
||||||
|
defaultCollapsed={false}
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full rounded-lg',
|
||||||
|
body: 'p-0',
|
||||||
|
title: 'px-2 py-1.5 font-normal text-sm',
|
||||||
|
collapsible: 'rounded-lg',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<table
|
<table
|
||||||
style={{
|
style={{
|
||||||
@@ -157,12 +167,12 @@ const SalesOrderProductTable = ({
|
|||||||
className='border-none w-full'
|
className='border-none w-full'
|
||||||
>
|
>
|
||||||
<tbody className='w-full'>
|
<tbody className='w-full'>
|
||||||
<tr className='border-b border-tools-table-outline border-base-content/5'>
|
<tr className='border-b border-t border-tools-table-outline border-base-content/5'>
|
||||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||||
Label
|
Label
|
||||||
</th>
|
</th>
|
||||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full mt-2'>
|
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||||
<div>Value</div>
|
<div>Value</div>
|
||||||
{formType !== 'success' && (
|
{formType !== 'success' && (
|
||||||
<div className='flex flex-row gap-1.5 items-center'>
|
<div className='flex flex-row gap-1.5 items-center'>
|
||||||
@@ -234,7 +244,7 @@ const SalesOrderProductTable = ({
|
|||||||
'ayam_pullet' && (
|
'ayam_pullet' && (
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Tipe Konversi</td>
|
<td className='text-sm px-4 py-3'>Tipe Konversi</td>
|
||||||
<td className='text-sm px-4 py-3'>{item.week?.label}</td>
|
<td className='text-sm px-4 py-3'>Week {item.week}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{item.convertion_unit?.value.toLowerCase() === 'peti' && (
|
{item.convertion_unit?.value.toLowerCase() === 'peti' && (
|
||||||
@@ -294,7 +304,7 @@ const SalesOrderProductTable = ({
|
|||||||
</>
|
</>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</Card>
|
||||||
))}
|
))}
|
||||||
{formType != 'add_deliver' &&
|
{formType != 'add_deliver' &&
|
||||||
formType != 'edit_deliver' &&
|
formType != 'edit_deliver' &&
|
||||||
|
|||||||
@@ -363,10 +363,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
accessorKey: 'location.name',
|
accessorKey: 'location.name',
|
||||||
header: 'Lokasi',
|
header: 'Lokasi',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorKey: 'fcr.name',
|
|
||||||
header: 'FCR',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'category',
|
accessorKey: 'category',
|
||||||
header: 'Kategori',
|
header: 'Kategori',
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ type ProjectFlockFormSchemaType = {
|
|||||||
label: string;
|
label: string;
|
||||||
} | null;
|
} | null;
|
||||||
category: string;
|
category: string;
|
||||||
fcr: {
|
|
||||||
value: number | string;
|
|
||||||
label: string;
|
|
||||||
} | null;
|
|
||||||
fcr_id: number;
|
|
||||||
production_standard: {
|
production_standard: {
|
||||||
value: number | string;
|
value: number | string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -96,15 +91,6 @@ export const ProjectFlockFormSchema: Yup.ObjectSchema<ProjectFlockFormSchemaType
|
|||||||
.oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
|
.oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
|
||||||
.required('Kategori wajib diisi!'),
|
.required('Kategori wajib diisi!'),
|
||||||
|
|
||||||
// FCR
|
|
||||||
fcr: Yup.object({
|
|
||||||
value: Yup.number().required('ID FCR wajib diisi!'),
|
|
||||||
label: Yup.string().required('Nama FCR wajib diisi!'),
|
|
||||||
}).nullable(),
|
|
||||||
fcr_id: Yup.number()
|
|
||||||
.min(1, 'FCR wajib diisi!')
|
|
||||||
.required('FCR wajib diisi!'),
|
|
||||||
|
|
||||||
// Production Standard
|
// Production Standard
|
||||||
production_standard: Yup.object({
|
production_standard: Yup.object({
|
||||||
value: Yup.number().required('ID Standar Produksi wajib diisi!'),
|
value: Yup.number().required('ID Standar Produksi wajib diisi!'),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
|||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
import {
|
import {
|
||||||
AreaApi,
|
AreaApi,
|
||||||
FcrApi,
|
|
||||||
FlockApi,
|
FlockApi,
|
||||||
KandangApi,
|
KandangApi,
|
||||||
LocationApi,
|
LocationApi,
|
||||||
@@ -284,13 +283,6 @@ const ProjectFlockForm = ({
|
|||||||
: ((initialValues?.area?.id ?? '') as string),
|
: ((initialValues?.area?.id ?? '') as string),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
|
||||||
options: optionsFcr,
|
|
||||||
isLoadingOptions: isLoadingFcrs,
|
|
||||||
setInputValue: setInputValueFcr,
|
|
||||||
loadMore: loadMoreFcr,
|
|
||||||
} = useSelect(FcrApi.basePath, 'id', 'name');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: optionsProductionStandards,
|
options: optionsProductionStandards,
|
||||||
isLoadingOptions: isLoadingProductionStandards,
|
isLoadingOptions: isLoadingProductionStandards,
|
||||||
@@ -505,12 +497,6 @@ const ProjectFlockForm = ({
|
|||||||
label: initialValues.category,
|
label: initialValues.category,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
fcr: initialValues?.fcr
|
|
||||||
? {
|
|
||||||
value: initialValues.fcr?.id,
|
|
||||||
label: initialValues.fcr.name,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
production_standard: initialValues?.production_standard
|
production_standard: initialValues?.production_standard
|
||||||
? {
|
? {
|
||||||
value: initialValues.production_standard?.id,
|
value: initialValues.production_standard?.id,
|
||||||
@@ -531,7 +517,6 @@ const ProjectFlockForm = ({
|
|||||||
category: initialValues?.category as NonNullable<
|
category: initialValues?.category as NonNullable<
|
||||||
'GROWING' | 'LAYING' | undefined
|
'GROWING' | 'LAYING' | undefined
|
||||||
>,
|
>,
|
||||||
fcr_id: initialValues?.fcr?.id ?? 0,
|
|
||||||
production_standard_id: initialValues?.production_standard?.id ?? 0,
|
production_standard_id: initialValues?.production_standard?.id ?? 0,
|
||||||
location_id: initialValues?.location?.id ?? 0,
|
location_id: initialValues?.location?.id ?? 0,
|
||||||
kandang_ids: initialValues?.kandangs?.map(
|
kandang_ids: initialValues?.kandangs?.map(
|
||||||
@@ -574,7 +559,6 @@ const ProjectFlockForm = ({
|
|||||||
flock_name: values.flock_name as string,
|
flock_name: values.flock_name as string,
|
||||||
area_id: values.area_id as number,
|
area_id: values.area_id as number,
|
||||||
category: values.category as string,
|
category: values.category as string,
|
||||||
fcr_id: values.fcr_id as number,
|
|
||||||
production_standard_id: values.production_standard_id as number,
|
production_standard_id: values.production_standard_id as number,
|
||||||
location_id: values.location_id as number,
|
location_id: values.location_id as number,
|
||||||
kandang_ids: values.kandang_ids as number[],
|
kandang_ids: values.kandang_ids as number[],
|
||||||
@@ -996,25 +980,6 @@ const ProjectFlockForm = ({
|
|||||||
isClearable
|
isClearable
|
||||||
isDisabled={formType != 'add'}
|
isDisabled={formType != 'add'}
|
||||||
/>
|
/>
|
||||||
<SelectInput
|
|
||||||
required
|
|
||||||
label='FCR'
|
|
||||||
placeholder='Pilih FCR'
|
|
||||||
value={formik.values.fcr as OptionType}
|
|
||||||
onChange={(val) => {
|
|
||||||
optionChangeHandler(val, 'fcr');
|
|
||||||
}}
|
|
||||||
onInputChange={setInputValueFcr}
|
|
||||||
onMenuScrollToBottom={loadMoreFcr}
|
|
||||||
options={optionsFcr}
|
|
||||||
isLoading={isLoadingFcrs}
|
|
||||||
isError={
|
|
||||||
formik.touched.fcr_id && Boolean(formik.errors.fcr_id)
|
|
||||||
}
|
|
||||||
errorMessage={formik.errors.fcr_id as string}
|
|
||||||
isClearable
|
|
||||||
isDisabled={formType != 'add'}
|
|
||||||
/>
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Kategori'
|
label='Kategori'
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ import {
|
|||||||
RecordingApi,
|
RecordingApi,
|
||||||
ProjectFlockApi,
|
ProjectFlockApi,
|
||||||
} from '@/services/api/production';
|
} from '@/services/api/production';
|
||||||
import { FcrApi, ProductionStandardApi } from '@/services/api/master-data';
|
import { ProductionStandardApi } from '@/services/api/master-data';
|
||||||
import { FcrWithStandards, FcrStandard } from '@/types/api/master-data/fcr';
|
|
||||||
import {
|
import {
|
||||||
ProductionStandard,
|
ProductionStandard,
|
||||||
StandardDetails,
|
StandardDetails,
|
||||||
@@ -87,24 +86,6 @@ interface RecordingFormProps {
|
|||||||
initialValues?: Recording;
|
initialValues?: Recording;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fcrStandardColumns: ColumnDef<FcrStandard>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: 'weight',
|
|
||||||
header: 'Weight',
|
|
||||||
cell: (props) => formatNumber(props.getValue() as number),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'fcr_number',
|
|
||||||
header: 'FCR Number',
|
|
||||||
cell: (props) => formatNumber(props.getValue() as number),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'mortality',
|
|
||||||
header: 'Mortality',
|
|
||||||
cell: (props) => formatNumber(props.getValue() as number),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const productionStandardColumns: ColumnDef<StandardDetails>[] = [
|
const productionStandardColumns: ColumnDef<StandardDetails>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'week',
|
accessorKey: 'week',
|
||||||
@@ -253,36 +234,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const approveModal = useModal();
|
const approveModal = useModal();
|
||||||
const rejectModal = useModal();
|
const rejectModal = useModal();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
const fcrStandardModal = useModal();
|
|
||||||
const productionStandardModal = useModal();
|
const productionStandardModal = useModal();
|
||||||
|
|
||||||
const [fcrStandards, setFcrStandards] = useState<FcrStandard[]>([]);
|
|
||||||
const [productionStandards, setProductionStandards] =
|
const [productionStandards, setProductionStandards] =
|
||||||
useState<ProductionStandard | null>(null);
|
useState<ProductionStandard | null>(null);
|
||||||
|
|
||||||
const [isFcrModalOpen, setIsFcrModalOpen] = useState(false);
|
|
||||||
const [isProductionStandardModalOpen, setIsProductionStandardModalOpen] =
|
const [isProductionStandardModalOpen, setIsProductionStandardModalOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkFcrModalOpen = () => {
|
|
||||||
const isOpen = fcrStandardModal.ref.current?.open || false;
|
|
||||||
setIsFcrModalOpen(isOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
checkFcrModalOpen();
|
|
||||||
|
|
||||||
const observer = new MutationObserver(checkFcrModalOpen);
|
|
||||||
if (fcrStandardModal.ref.current) {
|
|
||||||
observer.observe(fcrStandardModal.ref.current, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['open'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, [fcrStandardModal.ref]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkProductionStandardModalOpen = () => {
|
const checkProductionStandardModalOpen = () => {
|
||||||
const isOpen = productionStandardModal.ref.current?.open || false;
|
const isOpen = productionStandardModal.ref.current?.open || false;
|
||||||
@@ -460,24 +419,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
? projectFlockKandangLookupData.data
|
? projectFlockKandangLookupData.data
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const fcrId = useMemo(() => {
|
|
||||||
if (type === 'add') {
|
|
||||||
return projectFlockKandangLookup?.project_flock?.fcr?.id;
|
|
||||||
}
|
|
||||||
return initialValues?.project_flock?.fcr?.id;
|
|
||||||
}, [type, projectFlockKandangLookup, initialValues]);
|
|
||||||
|
|
||||||
const { data: fcr, isLoading: isLoadingFcrStandards } = useSWR(
|
|
||||||
isFcrModalOpen && fcrId ? `fcr-detail-${fcrId}` : null,
|
|
||||||
() => FcrApi.getSingle(fcrId!)
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (fcr?.status === 'success') {
|
|
||||||
setFcrStandards((fcr.data as FcrWithStandards).fcr_standards || []);
|
|
||||||
}
|
|
||||||
}, [fcr]);
|
|
||||||
|
|
||||||
const productionStandardId = useMemo(() => {
|
const productionStandardId = useMemo(() => {
|
||||||
if (type === 'add') {
|
if (type === 'add') {
|
||||||
return projectFlockKandangLookup?.project_flock?.production_standard_id;
|
return projectFlockKandangLookup?.project_flock?.production_standard_id;
|
||||||
@@ -606,7 +547,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
isLoadingOptions: isLoadingEggProducts,
|
isLoadingOptions: isLoadingEggProducts,
|
||||||
loadMore: loadMoreEggProducts,
|
loadMore: loadMoreEggProducts,
|
||||||
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
||||||
search: 'telur',
|
type: 'TELUR',
|
||||||
location_id: eggProductsLocationId,
|
location_id: eggProductsLocationId,
|
||||||
kandang_id: eggProductsKandangId,
|
kandang_id: eggProductsKandangId,
|
||||||
});
|
});
|
||||||
@@ -886,20 +827,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
if (isResponseSuccess(eggProductsData) && selectedKandang) {
|
if (isResponseSuccess(eggProductsData) && selectedKandang) {
|
||||||
const data = eggProductsData.data as unknown as ProductWarehouse[];
|
const data = eggProductsData.data as unknown as ProductWarehouse[];
|
||||||
data.forEach((product) => {
|
data.forEach((product) => {
|
||||||
const productName = product.product.name;
|
options.push({
|
||||||
|
value: product.id,
|
||||||
if (
|
label: product.product.name,
|
||||||
productName.toLowerCase().includes('telur') ||
|
});
|
||||||
productName.toLowerCase().includes('egg') ||
|
|
||||||
productName.toLowerCase().includes('pecah') ||
|
|
||||||
productName.toLowerCase().includes('konsumsi') ||
|
|
||||||
productName.toLowerCase().includes('baik')
|
|
||||||
) {
|
|
||||||
options.push({
|
|
||||||
value: product.id,
|
|
||||||
label: product.product.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1952,24 +1883,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
: '-'}
|
: '-'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<span className='text-sm text-gray-600'>Standard FCR</span>
|
|
||||||
<div className='mt-1'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color='primary'
|
|
||||||
className={{
|
|
||||||
badge:
|
|
||||||
'cursor-pointer hover:opacity-80 transition-opacity whitespace-nowrap',
|
|
||||||
}}
|
|
||||||
onClick={() => fcrStandardModal.openModal()}
|
|
||||||
>
|
|
||||||
{projectFlockKandangLookup?.project_flock?.fcr?.name ||
|
|
||||||
initialValues?.project_flock?.fcr?.name ||
|
|
||||||
'-'}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<span className='text-sm text-gray-600'>
|
<span className='text-sm text-gray-600'>
|
||||||
Standard Produksi
|
Standard Produksi
|
||||||
@@ -2160,22 +2073,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
|
||||||
<span className='text-sm text-gray-600'>Standard FCR</span>
|
|
||||||
<div className='mt-1'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color='primary'
|
|
||||||
className={{
|
|
||||||
badge:
|
|
||||||
'cursor-pointer hover:opacity-80 transition-opacity whitespace-nowrap',
|
|
||||||
}}
|
|
||||||
onClick={() => fcrStandardModal.openModal()}
|
|
||||||
>
|
|
||||||
{initialValues.project_flock?.fcr?.name || '-'}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<span className='text-sm text-gray-600'>
|
<span className='text-sm text-gray-600'>
|
||||||
Standard Produksi
|
Standard Produksi
|
||||||
@@ -2227,21 +2124,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
|
||||||
<td className='py-3 font-medium'>FCR (g)</td>
|
|
||||||
<td className='text-center py-3'>
|
|
||||||
<span className='font-semibold'>
|
|
||||||
{initialValues.fcr_value != null
|
|
||||||
? `${formatNumber(initialValues.fcr_value)} g`
|
|
||||||
: '-'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className='text-center py-3 text-gray-600'>
|
|
||||||
{initialValues.project_flock?.fcr?.fcr_std != null
|
|
||||||
? `${formatNumber(initialValues.project_flock?.fcr?.fcr_std)} g`
|
|
||||||
: '-'}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td className='py-3 font-medium'>Feed Intake (g)</td>
|
<td className='py-3 font-medium'>Feed Intake (g)</td>
|
||||||
<td className='text-center py-3'>
|
<td className='text-center py-3'>
|
||||||
@@ -3283,62 +3165,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* FCR Standard Modal */}
|
|
||||||
<Modal
|
|
||||||
ref={fcrStandardModal.ref}
|
|
||||||
closeOnBackdrop={true}
|
|
||||||
className={{
|
|
||||||
modal: 'p-0',
|
|
||||||
modalBox: 'p-0 rounded-2xl xl:max-w-4/12 max-w-sm',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='space-y-6'>
|
|
||||||
{/* Modal Header */}
|
|
||||||
<div className='flex items-center justify-between gap-2 py-3 border-b border-gray-300 px-4'>
|
|
||||||
<div className='flex items-center gap-2 text-primary'>
|
|
||||||
<Icon icon='mdi:chart-line' width={20} height={20} />
|
|
||||||
<h3 className='font-semibold'>Detail Standard FCR</h3>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant='link'
|
|
||||||
onClick={fcrStandardModal.closeModal}
|
|
||||||
className='text-gray-500 hover:text-gray-700 transition-colors cursor-pointer'
|
|
||||||
>
|
|
||||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className='px-4'>
|
|
||||||
{isLoadingFcrStandards ? (
|
|
||||||
<div className='flex justify-center py-8'>
|
|
||||||
<span className='loading loading-spinner loading-lg'></span>
|
|
||||||
</div>
|
|
||||||
) : fcrStandards.length > 0 ? (
|
|
||||||
<Table<FcrStandard>
|
|
||||||
data={fcrStandards}
|
|
||||||
columns={fcrStandardColumns}
|
|
||||||
pageSize={100}
|
|
||||||
className={{
|
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
|
||||||
tableClassName: 'w-full table-auto text-sm',
|
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
|
||||||
headerColumnClassName:
|
|
||||||
'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0',
|
|
||||||
bodyRowClassName:
|
|
||||||
'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200',
|
|
||||||
bodyColumnClassName:
|
|
||||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
|
||||||
paginationClassName: 'hidden',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<p className='text-sm text-gray-500'>
|
|
||||||
Tidak ada data FCR standards
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
{/* Production Standard Modal */}
|
{/* Production Standard Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
closeOnBackdrop={true}
|
closeOnBackdrop={true}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ChangeEventHandler, useCallback, useState } from 'react';
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
import useSWRInfinite from 'swr/infinite';
|
||||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
@@ -17,16 +18,19 @@ import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
|||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import StatusBadge from '@/components/helper/StatusBadge';
|
import StatusBadge from '@/components/helper/StatusBadge';
|
||||||
import PurchaseOrderInvoice from '@/components/pages/purchase/order/PurchaseOrderInvoice';
|
|
||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { Purchase } from '@/types/api/purchase/purchase';
|
import { Purchase } from '@/types/api/purchase/purchase';
|
||||||
import { PurchaseApi } from '@/services/api/purchase';
|
import { PurchaseApi } from '@/services/api/purchase';
|
||||||
|
import { ExpenseApi } from '@/services/api/expense';
|
||||||
|
import { Expense } from '@/types/api/expense';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
// ===== STATUS BADGE UTILITIES =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
@@ -159,27 +163,33 @@ const PurchaseTable = () => {
|
|||||||
PurchaseApi.getAllFetcher
|
PurchaseApi.getAllFetcher
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isDownloadingInvoice, setIsDownloadingInvoice] = useState(false);
|
const getKey = (
|
||||||
const [invoicePurchaseData, setInvoicePurchaseData] =
|
pageIndex: number,
|
||||||
useState<Purchase | null>(null);
|
previousPageData: BaseApiResponse<Expense>[] | null
|
||||||
|
) => {
|
||||||
const handleDownloadInvoice = async (purchaseId: number) => {
|
if (pageIndex > 0 && !previousPageData) return null;
|
||||||
setIsDownloadingInvoice(true);
|
return `${ExpenseApi.basePath}?page=${pageIndex + 1}&limit=100`;
|
||||||
try {
|
|
||||||
const response = await PurchaseApi.getSingle(purchaseId);
|
|
||||||
if (isResponseSuccess(response) && response.data) {
|
|
||||||
setInvoicePurchaseData(response.data);
|
|
||||||
setTimeout(() => {
|
|
||||||
setInvoicePurchaseData(null);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.error('Gagal mengambil data purchase order.');
|
|
||||||
} finally {
|
|
||||||
setIsDownloadingInvoice(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { data: expensesPages } = useSWRInfinite(
|
||||||
|
getKey,
|
||||||
|
ExpenseApi.getAllFetcher
|
||||||
|
);
|
||||||
|
|
||||||
|
const expenseMap = useMemo(() => {
|
||||||
|
const map = new Map<string, number>();
|
||||||
|
if (!expensesPages) return map;
|
||||||
|
|
||||||
|
expensesPages.forEach((page) => {
|
||||||
|
if (isResponseSuccess(page)) {
|
||||||
|
page.data.forEach((expense: Expense) => {
|
||||||
|
map.set(expense.reference_number, expense.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [expensesPages]);
|
||||||
|
|
||||||
// ===== TABLE COLUMNS DEFINITION =====
|
// ===== TABLE COLUMNS DEFINITION =====
|
||||||
const purchaseColumns: ColumnDef<Purchase>[] = [
|
const purchaseColumns: ColumnDef<Purchase>[] = [
|
||||||
{
|
{
|
||||||
@@ -191,37 +201,34 @@ const PurchaseTable = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'po_expedition',
|
accessorKey: 'po_expedition',
|
||||||
header: 'PO Ekspedisi',
|
header: 'Ekspedisi PO',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const purchase = props.row.original;
|
const poExpedition = props.row.original.po_expedition;
|
||||||
|
if (!poExpedition || poExpedition.length === 0) return '-';
|
||||||
if (!purchase.po_number || purchase.po_number === 'Belum dibuat') {
|
|
||||||
return <span>-</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<ul className='list-disc pl-4'>
|
||||||
color='primary'
|
{poExpedition.map((exp, index) => {
|
||||||
className='w-fit min-w-32 flex items-center justify-start gap-1 px-2 py-1 text-sm font-mono'
|
const expenseId = expenseMap.get(exp.refrence);
|
||||||
onClick={() => handleDownloadInvoice(purchase.id)}
|
if (expenseId) {
|
||||||
disabled={isDownloadingInvoice}
|
return (
|
||||||
>
|
<li key={index}>
|
||||||
<Icon
|
<Link
|
||||||
icon={
|
href={`/expense/detail/?expenseId=${expenseId}`}
|
||||||
isDownloadingInvoice
|
className='p-0 h-auto text-primary underline'
|
||||||
? 'eos-icons:loading'
|
>
|
||||||
: 'material-symbols:file-open-outline'
|
{exp.refrence}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
width={16}
|
return <li key={index}>{exp.refrence}</li>;
|
||||||
height={16}
|
})}
|
||||||
/>
|
</ul>
|
||||||
{purchase.po_number}
|
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'supplier.name',
|
accessorKey: 'supplier',
|
||||||
header: 'Vendor',
|
header: 'Vendor',
|
||||||
cell: (props) => props.row.original.supplier.name,
|
cell: (props) => props.row.original.supplier.name,
|
||||||
},
|
},
|
||||||
@@ -505,15 +512,6 @@ const PurchaseTable = () => {
|
|||||||
onClick: confirmationModalDeleteClickHandler,
|
onClick: confirmationModalDeleteClickHandler,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{invoicePurchaseData && (
|
|
||||||
<div className='hidden'>
|
|
||||||
<PurchaseOrderInvoice
|
|
||||||
data={invoicePurchaseData}
|
|
||||||
triggerDownloadOnMount={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Page,
|
Page,
|
||||||
Text,
|
Text,
|
||||||
@@ -235,16 +235,11 @@ const pdfStyles = StyleSheet.create({
|
|||||||
interface PurchaseOrderInvoiceProps {
|
interface PurchaseOrderInvoiceProps {
|
||||||
data?: Purchase;
|
data?: Purchase;
|
||||||
className?: string;
|
className?: string;
|
||||||
triggerDownloadOnMount?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PurchaseOrderInvoice = ({
|
const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
|
||||||
data,
|
|
||||||
triggerDownloadOnMount,
|
|
||||||
}: PurchaseOrderInvoiceProps) => {
|
|
||||||
const [, setIsGeneratingPDF] = useState(false);
|
const [, setIsGeneratingPDF] = useState(false);
|
||||||
const purchaseData = data;
|
const purchaseData = data;
|
||||||
const hasDownloadedRef = useRef(false);
|
|
||||||
|
|
||||||
const grandTotal = useMemo(() => {
|
const grandTotal = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@@ -255,7 +250,7 @@ const PurchaseOrderInvoice = ({
|
|||||||
);
|
);
|
||||||
}, [purchaseData?.items]);
|
}, [purchaseData?.items]);
|
||||||
|
|
||||||
const handleDownloadPDF = useCallback(async () => {
|
const handleDownloadPDF = async () => {
|
||||||
if (!purchaseData) {
|
if (!purchaseData) {
|
||||||
toast.error('No purchase order data available');
|
toast.error('No purchase order data available');
|
||||||
return;
|
return;
|
||||||
@@ -515,20 +510,7 @@ const PurchaseOrderInvoice = ({
|
|||||||
} finally {
|
} finally {
|
||||||
setIsGeneratingPDF(false);
|
setIsGeneratingPDF(false);
|
||||||
}
|
}
|
||||||
}, [purchaseData]);
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (triggerDownloadOnMount && purchaseData && !hasDownloadedRef.current) {
|
|
||||||
hasDownloadedRef.current = true;
|
|
||||||
handleDownloadPDF();
|
|
||||||
}
|
|
||||||
}, [triggerDownloadOnMount, purchaseData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!triggerDownloadOnMount) {
|
|
||||||
hasDownloadedRef.current = false;
|
|
||||||
}
|
|
||||||
}, [triggerDownloadOnMount]);
|
|
||||||
|
|
||||||
if (!purchaseData) {
|
if (!purchaseData) {
|
||||||
return (
|
return (
|
||||||
@@ -538,10 +520,6 @@ const PurchaseOrderInvoice = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerDownloadOnMount) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return purchaseData?.po_number &&
|
return purchaseData?.po_number &&
|
||||||
purchaseData.po_number !== 'Belum dibuat' ? (
|
purchaseData.po_number !== 'Belum dibuat' ? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export type MarketingFormValues = {
|
|||||||
total_price?: string | number;
|
total_price?: string | number;
|
||||||
marketing_type?: { value: string; label: string } | null;
|
marketing_type?: { value: string; label: string } | null;
|
||||||
convertion_unit?: { value: string; label: string } | null;
|
convertion_unit?: { value: string; label: string } | null;
|
||||||
week?: { value?: number; label?: string } | null;
|
week?: number | null;
|
||||||
weight_per_convertion?: number | null;
|
weight_per_convertion?: number | null;
|
||||||
price_per_convertion?: number | null;
|
price_per_convertion?: number | null;
|
||||||
total_peti?: number | null;
|
total_peti?: number | null;
|
||||||
@@ -100,7 +100,7 @@ export const calculateAyamPullet = (
|
|||||||
): void => {
|
): void => {
|
||||||
const { values, setFieldValue } = ctx;
|
const { values, setFieldValue } = ctx;
|
||||||
const unitPrice = Number(values.unit_price || 0);
|
const unitPrice = Number(values.unit_price || 0);
|
||||||
const week = Number(values.week?.value || 0);
|
const week = Number(values.week || 0);
|
||||||
const qty = Number(values.qty || 0);
|
const qty = Number(values.qty || 0);
|
||||||
const avgWeight = Number(values.avg_weight || 0);
|
const avgWeight = Number(values.avg_weight || 0);
|
||||||
const totalWeight = Number(values.total_weight || 0);
|
const totalWeight = Number(values.total_weight || 0);
|
||||||
|
|||||||
+2
@@ -17,6 +17,8 @@ export type BaseMarketing = {
|
|||||||
status?: string;
|
status?: string;
|
||||||
so_number: string;
|
so_number: string;
|
||||||
so_date: string;
|
so_date: string;
|
||||||
|
do_number?: string;
|
||||||
|
do_date?: string;
|
||||||
customer: Customer;
|
customer: Customer;
|
||||||
sales_person: CreatedUser;
|
sales_person: CreatedUser;
|
||||||
notes: string;
|
notes: string;
|
||||||
|
|||||||
-4
@@ -1,5 +1,4 @@
|
|||||||
import { Area } from '@/types/api/master-data/area';
|
import { Area } from '@/types/api/master-data/area';
|
||||||
import { Fcr } from '@/types/api/master-data/fcr';
|
|
||||||
import { Flock } from '@/types/api/master-data/flock';
|
import { Flock } from '@/types/api/master-data/flock';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import { Location } from '@/types/api/master-data/location';
|
import { Location } from '@/types/api/master-data/location';
|
||||||
@@ -16,8 +15,6 @@ export type BaseProjectFlock = {
|
|||||||
area: Area;
|
area: Area;
|
||||||
area_id: number;
|
area_id: number;
|
||||||
category: string;
|
category: string;
|
||||||
fcr: Fcr;
|
|
||||||
fcr_id: number;
|
|
||||||
production_standard: ProductionStandard;
|
production_standard: ProductionStandard;
|
||||||
production_standard_id: number;
|
production_standard_id: number;
|
||||||
location: Location;
|
location: Location;
|
||||||
@@ -51,7 +48,6 @@ export type CreateProjectFlockPayload = {
|
|||||||
flock_name: string;
|
flock_name: string;
|
||||||
area_id: number;
|
area_id: number;
|
||||||
category: string;
|
category: string;
|
||||||
fcr_id: number;
|
|
||||||
production_standard_id: number;
|
production_standard_id: number;
|
||||||
location_id: number;
|
location_id: number;
|
||||||
kandang_ids: number[];
|
kandang_ids: number[];
|
||||||
|
|||||||
Vendored
+1
-1
@@ -76,7 +76,7 @@ export type BasePurchase = {
|
|||||||
items?: PurchaseItem[];
|
items?: PurchaseItem[];
|
||||||
latest_approval?: BaseApproval;
|
latest_approval?: BaseApproval;
|
||||||
requester_name?: string;
|
requester_name?: string;
|
||||||
po_expedition?: string[];
|
po_expedition?: { id: number; refrence: string }[];
|
||||||
created_user?: CreatedUser;
|
created_user?: CreatedUser;
|
||||||
products?: PurchaseItemProduct[];
|
products?: PurchaseItemProduct[];
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user