mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-114): integrate product stock fetching and selection in RecordingForm
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState, useEffect } from 'react';
|
||||
import { useMemo, useState, useEffect, useCallback } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { useRecordingFormHandlers } from './useRecordingFormHandlers';
|
||||
import { ProjectFlockApi } from '@/services/api/production';
|
||||
import { LocationApi } from '@/services/api/master-data';
|
||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
@@ -71,6 +72,22 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
ProjectFlockApi.getAllFetcher
|
||||
);
|
||||
|
||||
// Fetch Products with location filter (both PAKAN and OVK) - using selectedLocation for now
|
||||
const stockProductsUrl = useMemo(() => {
|
||||
if (!selectedLocation) return null;
|
||||
const params = new URLSearchParams({
|
||||
flags: 'PAKAN,OVK', // Fetch both flags in one request
|
||||
search: '',
|
||||
location_id: selectedLocation.value.toString(),
|
||||
});
|
||||
return `${ProductWarehouseApi.basePath}?${params.toString()}`;
|
||||
}, [selectedLocation]);
|
||||
|
||||
const { data: stockProducts, isLoading: isLoadingStockProducts } = useSWR(
|
||||
stockProductsUrl,
|
||||
ProductWarehouseApi.getAllFetcher
|
||||
);
|
||||
|
||||
// Extract location options from locations data
|
||||
const locationOptions = useMemo(() => {
|
||||
if (!isResponseSuccess(locations)) return [];
|
||||
@@ -99,6 +116,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
return options;
|
||||
}, [projectFlocks]);
|
||||
|
||||
|
||||
const {
|
||||
deleteModal,
|
||||
recordingFormErrorMessage,
|
||||
@@ -179,6 +197,26 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
// Get location from selected project flock for stock filtering
|
||||
const getProjectFlockLocation = useCallback((): OptionType | null => {
|
||||
if (!formik.values.project_flock_kandang || !isResponseSuccess(projectFlocks)) {
|
||||
return selectedLocation; // Fallback to manual location selection
|
||||
}
|
||||
|
||||
const kandangId = formik.values.project_flock_kandang.value;
|
||||
for (const projectFlock of projectFlocks.data) {
|
||||
const kandang = projectFlock.kandangs.find(k => k.id === kandangId);
|
||||
if (kandang && projectFlock.location) {
|
||||
return {
|
||||
value: projectFlock.location.id,
|
||||
label: projectFlock.location.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return selectedLocation; // Fallback to manual location selection
|
||||
}, [formik.values.project_flock_kandang, projectFlocks, selectedLocation]);
|
||||
|
||||
// EVENT HANDLERS - Select Inputs
|
||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedLocation(val as OptionType);
|
||||
@@ -386,6 +424,40 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
formik.setFieldValue('stocks', newStocks);
|
||||
};
|
||||
|
||||
// Memoized unified products for stock selection
|
||||
const unifiedStockProducts = useMemo(() => {
|
||||
if (!isResponseSuccess(stockProducts)) return [];
|
||||
const options: OptionType[] = [];
|
||||
|
||||
stockProducts.data.forEach((product) => {
|
||||
const warehouse = product.warehouse;
|
||||
const stockText = product.quantity.toLocaleString('id-ID');
|
||||
|
||||
// Check if product has any of the flags
|
||||
const hasPakanFlag = product.product.flags?.includes('PAKAN');
|
||||
const hasOvkFlag = product.product.flags?.includes('OVK');
|
||||
|
||||
// Add products with warehouse and location grouping in label (similar to projectFlockKandangOptions pattern)
|
||||
if (hasPakanFlag) {
|
||||
options.push({
|
||||
value: product.id,
|
||||
label: `[PAKAN] ${product.product.name} - ${warehouse?.name || ''} (${stockText})`
|
||||
});
|
||||
}
|
||||
|
||||
if (hasOvkFlag) {
|
||||
options.push({
|
||||
value: product.id,
|
||||
label: `[OVK] ${product.product.name} - ${warehouse?.name || ''} (${stockText})`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return options;
|
||||
}, [stockProducts, getProjectFlockLocation()]);
|
||||
|
||||
|
||||
// Unified Stock remove handlers
|
||||
const removeStock = (idx: number) => {
|
||||
const updatedStocks = formik.values.stocks?.filter((_, i) => i !== idx);
|
||||
formik.setFieldValue('stocks', updatedStocks);
|
||||
@@ -888,13 +960,24 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</td>
|
||||
)}
|
||||
<td>
|
||||
<TextInput
|
||||
<SelectInput
|
||||
required
|
||||
name={`stocks.${idx}.product_warehouse_id`}
|
||||
type='number'
|
||||
value={stock.product_warehouse_id?.toString() || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
value={
|
||||
unifiedStockProducts.find(
|
||||
(product) =>
|
||||
product.value === stock.product_warehouse_id
|
||||
) || null
|
||||
}
|
||||
onChange={(selectedOption) => {
|
||||
const option = selectedOption as OptionType | null;
|
||||
formik.setFieldValue(
|
||||
`stocks.${idx}.product_warehouse_id`,
|
||||
option?.value || 0
|
||||
);
|
||||
}}
|
||||
options={unifiedStockProducts}
|
||||
placeholder='Pilih Produk'
|
||||
isLoading={isLoadingStockProducts}
|
||||
isError={
|
||||
isRepeaterInputError(
|
||||
'stocks',
|
||||
@@ -909,24 +992,23 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
idx
|
||||
).errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-32',
|
||||
wrapper: 'w-full min-w-48',
|
||||
}}
|
||||
placeholder='Product Warehouse ID'
|
||||
isSearchable
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
<NumberInput
|
||||
name={`stocks.${idx}.increase`}
|
||||
type='number'
|
||||
value={
|
||||
typeof stock.increase === 'number'
|
||||
? stock.increase.toString()
|
||||
: stock.increase
|
||||
}
|
||||
value={stock.increase || 0}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='number'
|
||||
decimals={0}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('stocks', 'increase', idx)
|
||||
.isError
|
||||
@@ -943,16 +1025,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
<NumberInput
|
||||
name={`stocks.${idx}.decrease`}
|
||||
type='number'
|
||||
value={
|
||||
typeof stock.decrease === 'number'
|
||||
? stock.decrease.toString()
|
||||
: stock.decrease
|
||||
}
|
||||
value={stock.decrease || 0}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='number'
|
||||
decimals={0}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('stocks', 'decrease', idx)
|
||||
.isError
|
||||
@@ -969,16 +1051,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
<NumberInput
|
||||
name={`stocks.${idx}.usage_amount`}
|
||||
type='number'
|
||||
value={
|
||||
typeof stock.usage_amount === 'number'
|
||||
? stock.usage_amount.toString()
|
||||
: stock.usage_amount
|
||||
}
|
||||
value={stock.usage_amount || 0}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='number'
|
||||
decimals={0}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('stocks', 'usage_amount', idx)
|
||||
.isError
|
||||
|
||||
Reference in New Issue
Block a user