refactor(FE): Refactor selects to use useSelect hook

This commit is contained in:
rstubryan
2026-01-15 10:44:27 +07:00
parent 3bc5030a3d
commit a1301121ac
@@ -12,7 +12,10 @@ import RequirePermission from '@/components/helper/RequirePermission';
import Card from '@/components/Card';
import Badge from '@/components/Badge';
import NumberInput from '@/components/input/NumberInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import CheckboxInput from '@/components/input/CheckboxInput';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
@@ -26,6 +29,7 @@ import {
} from '@/services/api/production';
import { LocationApi } from '@/services/api/master-data';
import { ProductWarehouseApi } from '@/services/api/inventory';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import {
CreateGrowingRecordingPayload,
@@ -36,7 +40,10 @@ import {
NextDayRecording,
} from '@/types/api/production/recording';
import { type BaseApiResponse } from '@/types/api/api-general';
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
import {
ProjectFlockKandangLookup,
ProjectFlock,
} from '@/types/api/production/project-flock';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { Kandang } from '@/types/api/master-data/kandang';
@@ -77,16 +84,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const [selectedDepletions, setSelectedDepletions] = useState<number[]>([]);
const [selectedEggs, setSelectedEggs] = useState<number[]>([]);
const [locationSearchValue, setLocationSearchValue] = useState('');
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null
);
const [projectFlockSearchValue, setProjectFlockSearchValue] = useState('');
const [selectedProjectFlock, setSelectedProjectFlock] =
useState<OptionType | null>(null);
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(
null
);
const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] =
useState<string>('');
const [stockProductsLocationId, setStockProductsLocationId] =
useState<string>('');
const [stockProductsKandangId, setStockProductsKandangId] =
useState<string>('');
const [depletionProductsLocationId, setDepletionProductsLocationId] =
useState<string>('');
const [depletionProductsKandangId, setDepletionProductsKandangId] =
useState<string>('');
const [eggProductsLocationId, setEggProductsLocationId] =
useState<string>('');
const [eggProductsKandangId, setEggProductsKandangId] = useState<string>('');
const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isRejectLoading, setIsRejectLoading] = useState(false);
@@ -210,26 +228,24 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}, [deleteModal, initialValues?.id, router]);
// ===== API DATA FETCHING =====
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({
search: locationSearchValue || '',
limit: '100',
}).toString()}`;
const { data: locations, isLoading: isLoadingLocations } = useSWR(
locationsUrl,
LocationApi.getAllFetcher
);
const {
setInputValue: setLocationSearchValue,
options: locationOptions,
isLoadingOptions: isLoadingLocations,
loadMore: loadMoreLocations,
hasMore: hasMoreLocations,
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
const projectFlocksUrl = `${ProjectFlockApi.basePath}?${new URLSearchParams({
search: projectFlockSearchValue || '',
limit: '100',
...(selectedLocation
? { location_id: selectedLocation.value.toString() }
: {}),
}).toString()}`;
const { data: projectFlocks, isLoading: isLoadingProjectFlocks } = useSWR(
projectFlocksUrl,
ProjectFlockApi.getAllFetcher
);
const {
setInputValue: setProjectFlockSearchValue,
options: projectFlockOptions,
rawData: projectFlocksRawData,
isLoadingOptions: isLoadingProjectFlocks,
loadMore: loadMoreProjectFlocks,
hasMore: hasMoreProjectFlocks,
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
location_id: selectedProjectFlockLocationId,
});
const projectFlockKandangLookupUrl = useMemo(() => {
if (!selectedProjectFlock || !selectedKandang) return null;
@@ -279,46 +295,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
? projectFlockKandangDetailData.data
: undefined;
const stockProductsUrl = useMemo(() => {
if (!selectedLocation || !selectedKandang) return null;
const params = new URLSearchParams({
flags: 'PAKAN,OVK',
search: '',
limit: '100',
location_id: selectedLocation.value.toString(),
});
const {
options: stockProductOptions,
rawData: stockProducts,
isLoadingOptions: isLoadingStockProducts,
loadMore: loadMoreStockProducts,
hasMore: hasMoreStockProducts,
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
flags: 'PAKAN,OVK',
location_id: stockProductsLocationId,
kandang_id: stockProductsKandangId,
});
if (projectFlockKandangLookup?.kandang?.id) {
params.append(
'kandang_id',
projectFlockKandangLookup.kandang.id.toString()
);
} else if (selectedKandang) {
params.append('kandang_id', selectedKandang.value.toString());
}
return `${ProductWarehouseApi.basePath}?${params.toString()}`;
}, [selectedLocation, selectedKandang, projectFlockKandangLookup]);
const depletionProductsUrl = useMemo(() => {
if (!selectedLocation || !selectedKandang) return null;
const params = new URLSearchParams({
search: '',
limit: '100',
location_id: selectedLocation.value.toString(),
});
if (projectFlockKandangLookup?.kandang?.id) {
params.append(
'kandang_id',
projectFlockKandangLookup.kandang.id.toString()
);
} else if (selectedKandang) {
params.append('kandang_id', selectedKandang.value.toString());
}
return `${ProductWarehouseApi.basePath}?${params.toString()}`;
}, [selectedLocation, selectedKandang, projectFlockKandangLookup]);
const {
options: depletionProductOptions,
rawData: depletionProductsData,
isLoadingOptions: isLoadingDepletionProducts,
loadMore: loadMoreDepletionProducts,
hasMore: hasMoreDepletionProducts,
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
location_id: depletionProductsLocationId,
kandang_id: depletionProductsKandangId,
});
const today = new Date().toISOString().split('T')[0];
const existingRecordingsUrl = `${RecordingApi.basePath}`;
@@ -360,38 +358,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}
}, [nextDayRecordingData]);
const { data: stockProducts, isLoading: isLoadingStockProducts } = useSWR(
stockProductsUrl,
ProductWarehouseApi.getAllFetcher
);
const { data: depletionProductsData, isLoading: isLoadingDepletionProducts } =
useSWR(depletionProductsUrl, ProductWarehouseApi.getAllFetcher);
const eggProductsUrl = useMemo(() => {
if (!selectedLocation || !selectedKandang) return null;
const params = new URLSearchParams({
search: 'telur',
limit: '100',
location_id: selectedLocation.value.toString(),
});
if (projectFlockKandangLookup?.kandang?.id) {
params.append(
'kandang_id',
projectFlockKandangLookup.kandang.id.toString()
);
} else if (selectedKandang) {
params.append('kandang_id', selectedKandang.value.toString());
}
return `${ProductWarehouseApi.basePath}?${params.toString()}`;
}, [selectedLocation, selectedKandang, projectFlockKandangLookup]);
const { data: eggProductsData, isLoading: isLoadingEggProducts } = useSWR(
eggProductsUrl,
ProductWarehouseApi.getAllFetcher
);
const {
options: eggProductOptions,
rawData: eggProductsData,
isLoadingOptions: isLoadingEggProducts,
loadMore: loadMoreEggProducts,
hasMore: hasMoreEggProducts,
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
search: 'telur',
location_id: eggProductsLocationId,
kandang_id: eggProductsKandangId,
});
const approvedProjectFlockKandangsUrl = useMemo(() => {
const params = new URLSearchParams({
@@ -448,17 +425,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
});
// ===== DATA PROCESSING =====
const locationOptions = useMemo(() => {
let options: OptionType[] = [];
if (isResponseSuccess(locations)) {
const locationOptionsList =
locations?.data.map((location) => ({
value: location.id,
label: location.name || '',
})) || [];
options = options.concat(locationOptionsList);
}
const enhancedLocationOptions = useMemo(() => {
const options = [...locationOptions];
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
const currentLocation = projectFlockKandangDetail.project_flock.location;
@@ -474,19 +442,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}
return options;
}, [locations, projectFlockKandangDetail, type]);
}, [locationOptions, projectFlockKandangDetail, type]);
const projectFlockOptions = useMemo(() => {
let options: OptionType[] = [];
if (isResponseSuccess(projectFlocks)) {
const flockOptions =
projectFlocks?.data.map((projectFlock) => ({
value: projectFlock.id,
label: projectFlock.flock_name || '',
})) || [];
options = options.concat(flockOptions);
}
const enhancedProjectFlockOptions = useMemo(() => {
const options = [...projectFlockOptions];
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
const currentProjectFlock = projectFlockKandangDetail.project_flock;
@@ -502,13 +461,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}
return options;
}, [projectFlocks, projectFlockKandangDetail, type]);
}, [projectFlockOptions, projectFlockKandangDetail, type]);
const kandangOptions = useMemo(() => {
let options: OptionType[] = [];
if (selectedProjectFlock && isResponseSuccess(projectFlocks)) {
const selectedProjectFlockData = projectFlocks.data.find(
if (selectedProjectFlock && isResponseSuccess(projectFlocksRawData)) {
const data = projectFlocksRawData.data as ProjectFlock[];
const selectedProjectFlockData = data.find(
(pf) => pf.id === selectedProjectFlock.value
);
@@ -548,7 +508,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return options;
}, [
selectedProjectFlock,
projectFlocks,
projectFlocksRawData,
projectFlockKandangDetail,
type,
approvedProjectFlockKandangs,
@@ -598,20 +558,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
]);
const unifiedStockProducts = useMemo(() => {
const options: OptionType[] = [];
if (isResponseSuccess(stockProducts) && selectedKandang) {
stockProducts.data.forEach((product) => {
const hasPakanFlag = product.product.flags?.includes('PAKAN');
const hasOvkFlag = product.product.flags?.includes('OVK');
if (hasPakanFlag || hasOvkFlag) {
options.push({
value: product.id,
label: product.product.name,
});
}
});
}
const options = [...stockProductOptions];
if (
initialValues &&
@@ -635,12 +582,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}
return options;
}, [stockProducts, initialValues, type, selectedKandang]);
}, [stockProductOptions, initialValues, type]);
const depletionProducts = useMemo(() => {
const options: OptionType[] = [];
if (isResponseSuccess(depletionProductsData) && selectedKandang) {
depletionProductsData.data.forEach((product) => {
const data = depletionProductsData.data as unknown as ProductWarehouse[];
data.forEach((product) => {
const productName = product.product.name;
if (
@@ -680,8 +629,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const eggProducts = useMemo(() => {
const options: OptionType[] = [];
if (isResponseSuccess(eggProductsData) && selectedKandang) {
eggProductsData.data.forEach((product) => {
const data = eggProductsData.data as unknown as ProductWarehouse[];
data.forEach((product) => {
const productName = product.product.name;
if (
@@ -812,33 +763,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
};
// ===== HELPER FUNCTIONS =====
useCallback((): OptionType | null => {
if (
!formik.values.project_flock_kandang ||
!isResponseSuccess(projectFlocks)
) {
return selectedLocation;
}
const projectFlockId = formik.values.project_flock_kandang.value;
const projectFlock = projectFlocks.data.find(
(pf) => pf.id === projectFlockId
);
if (projectFlock && projectFlock.location) {
return {
value: projectFlock.location.id,
label: projectFlock.location.name,
};
}
return selectedLocation;
}, [formik.values.project_flock_kandang, projectFlocks, selectedLocation]);
const getAvailableStock = useCallback(
(productWarehouseId: number) => {
if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0;
if (!isResponseSuccess(stockProducts)) return 0;
const productWarehouse = stockProducts.data.find(
(pw) => pw.id === productWarehouseId
);
const data = stockProducts.data as unknown as ProductWarehouse[];
const productWarehouse = data.find((pw) => pw.id === productWarehouseId);
return productWarehouse?.quantity ?? 0;
},
[stockProducts, type]
@@ -915,9 +845,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
(productWarehouseId: number) => {
if (!isResponseSuccess(stockProducts)) return null;
const productWarehouse = stockProducts.data.find(
(pw) => pw.id === productWarehouseId
);
const data = stockProducts.data as unknown as ProductWarehouse[];
const productWarehouse = data.find((pw) => pw.id === productWarehouseId);
if (!productWarehouse) return null;
const hasPakanFlag = productWarehouse.product.flags?.includes('PAKAN');
@@ -1002,9 +931,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// ===== EVENT HANDLERS =====
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedLocation(val as OptionType);
const location = val as OptionType;
setSelectedLocation(location);
setSelectedProjectFlock(null);
setSelectedKandang(null);
setSelectedProjectFlockLocationId(
location ? location.value.toString() : ''
);
formik.setFieldValue('project_flock_kandang', null);
formik.setFieldValue('project_flock_kandang_id', 0);
};
@@ -1017,7 +950,23 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
};
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedKandang(val as OptionType);
const kandang = val as OptionType;
setSelectedKandang(kandang);
if (selectedLocation && kandang) {
setStockProductsLocationId(selectedLocation.value.toString());
setStockProductsKandangId(kandang.value.toString());
setDepletionProductsLocationId(selectedLocation.value.toString());
setDepletionProductsKandangId(kandang.value.toString());
setEggProductsLocationId(selectedLocation.value.toString());
setEggProductsKandangId(kandang.value.toString());
} else {
setStockProductsLocationId('');
setStockProductsKandangId('');
setDepletionProductsLocationId('');
setDepletionProductsKandangId('');
setEggProductsLocationId('');
setEggProductsKandangId('');
}
formik.setFieldTouched('project_flock_kandang', true);
formik.setFieldTouched('project_flock_kandang_id', true);
};
@@ -1091,6 +1040,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
label: location.name || '',
};
setSelectedLocation(locationOption);
setSelectedProjectFlockLocationId(location.id.toString());
if (projectFlock) {
const projectFlockOption = {
@@ -1106,6 +1056,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
};
setSelectedKandang(kandangOption);
setStockProductsLocationId(location.id.toString());
setStockProductsKandangId(kandang.id.toString());
setDepletionProductsLocationId(location.id.toString());
setDepletionProductsKandangId(kandang.id.toString());
setEggProductsLocationId(location.id.toString());
setEggProductsKandangId(kandang.id.toString());
if (
formik.values.project_flock_kandang_id !==
projectFlockKandangDetail.id
@@ -1126,7 +1083,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}, [
projectFlockKandangDetail,
type,
projectFlockOptions,
enhancedProjectFlockOptions,
formik.values.project_flock_kandang_id,
]);
@@ -1415,23 +1372,25 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
label='Lokasi'
value={selectedLocation}
onChange={locationChangeHandler}
options={locationOptions}
options={enhancedLocationOptions}
onInputChange={setLocationSearchValue}
isLoading={isLoadingLocations}
onMenuScrollToBottom={loadMoreLocations}
placeholder='Pilih Lokasi'
isClearable
isSearchable
/>
<SelectInput
key={`project-flock-select-${selectedProjectFlock?.value || 'default'}-${projectFlockOptions.length}`}
key={`project-flock-select-${selectedProjectFlock?.value || 'default'}-${enhancedProjectFlockOptions.length}`}
required
label='Project Flock'
value={selectedProjectFlock}
onChange={projectFlockChangeHandler}
options={projectFlockOptions}
options={enhancedProjectFlockOptions}
onInputChange={setProjectFlockSearchValue}
isLoading={isLoadingProjectFlocks}
onMenuScrollToBottom={loadMoreProjectFlocks}
isDisabled={!selectedLocation}
placeholder={
selectedLocation