diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index c8adef19..382d609a 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -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); @@ -467,7 +539,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; }; - + return ( <>
@@ -534,8 +606,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={ formik.values.record_datetime instanceof Date ? formik.values.record_datetime - .toISOString() - .substring(0, 16) + .toISOString() + .substring(0, 16) : '' } onChange={(e) => { @@ -583,202 +655,202 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - {type !== 'detail' && ( - - )} + + {type !== 'detail' && ( + )} + - + - + - - {type !== 'detail' && } - + + + {type !== 'detail' && } + - {formik.values.body_weights?.map((bw, idx) => ( - - {type !== 'detail' && ( - - )} - + {type !== 'detail' && ( + + )} + + required + name={`body_weights.${idx}.weight`} + value={bw.weight} + onChange={handleWeightChangeWrapper(idx)} + onBlur={formik.handleBlur} + maskType='weight' + weightUnit='gram' + decimals={2} + min={0} + thousandSeparator=',' + decimalSeparator='.' + isError={ + isRepeaterInputError('body_weights', 'weight', idx) + .isError + } + errorMessage={ + isRepeaterInputError('body_weights', 'weight', idx) + .errorMessage + } + readOnly={type === 'detail'} + className={{ + wrapper: 'w-full min-w-32', + }} + /> + + + + + {type !== 'detail' && ( - - - {type !== 'detail' && ( - - )} - - ))} + )} + + ))}
- 0 - } - onChange={(e: React.ChangeEvent) => { - if (e.target.checked) { - setSelectedBodyWeights( - formik.values.body_weights?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedBodyWeights([]); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> -
- Berat Ayam (gram) - + 0 + } + onChange={(e: React.ChangeEvent) => { + if (e.target.checked) { + setSelectedBodyWeights( + formik.values.body_weights?.map( + (_, idx) => idx + ) ?? [] + ); + } else { + setSelectedBodyWeights([]); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + + Berat Ayam (gram) + * - - Jumlah Ayam - + + Jumlah Ayam + * - - Rata-rata Berat Ayam (gram) - + + Rata-rata Berat Ayam (gram) + - CatatanAction
CatatanAction
- ) => { - if (e.target.checked) { - setSelectedBodyWeights([ - ...selectedBodyWeights, - idx, - ]); - } else { - setSelectedBodyWeights( - selectedBodyWeights.filter((i) => i !== idx), - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - + {formik.values.body_weights?.map((bw, idx) => ( +
+ ) => { + if (e.target.checked) { + setSelectedBodyWeights([ + ...selectedBodyWeights, + idx, + ]); + } else { + setSelectedBodyWeights( + selectedBodyWeights.filter((i) => i !== idx), + ); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + - + + + { + handleAverageWeightBlur(); + formik.handleBlur(e); + }} + maskType='weight' + weightUnit='gram' + decimals={2} + min={0} + thousandSeparator=',' + decimalSeparator='.' + isError={ + isRepeaterInputError('body_weights', 'average_weight', idx) + .isError + } + errorMessage={ + isRepeaterInputError('body_weights', 'average_weight', idx) + .errorMessage + } + readOnly={type === 'detail'} + className={{ + wrapper: 'w-full min-w-32', + }} + /> + + + - +
+ +
- { - handleAverageWeightBlur(); - formik.handleBlur(e); - }} - maskType='weight' - weightUnit='gram' - decimals={2} - min={0} - thousandSeparator=',' - decimalSeparator='.' - isError={ - isRepeaterInputError('body_weights', 'average_weight', idx) - .isError - } - errorMessage={ - isRepeaterInputError('body_weights', 'average_weight', idx) - .errorMessage - } - readOnly={type === 'detail'} - className={{ - wrapper: 'w-full min-w-32', - }} - /> - - - -
- -
-
@@ -820,208 +892,218 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - {type !== 'detail' && ( - - )} + + {type !== 'detail' && ( + )} + - - - - - {type !== 'detail' && } - + + + + + + {type !== 'detail' && } + - {formik.values.stocks?.map((stock, idx) => ( - - {type !== 'detail' && ( - - )} - + {type !== 'detail' && ( + + )} + + + + + + name={`stocks.${idx}.notes`} + value={stock.notes || ''} + onChange={formik.handleChange} + onBlur={formik.handleBlur} + placeholder='Catatan...' + readOnly={type === 'detail'} + className={{ + wrapper: 'w-full min-w-32', + }} + /> + + {type !== 'detail' && ( - - - - {type !== 'detail' && ( - - )} - - ))} + )} + + ))}
- 0 - } - onChange={(e: React.ChangeEvent) => { - if (e.target.checked) { - setSelectedStocks( - formik.values.stocks?.map((_, idx) => idx) ?? - [] - ); - } else { - setSelectedStocks([]); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> -
- Persediaan - + 0 + } + onChange={(e: React.ChangeEvent) => { + if (e.target.checked) { + setSelectedStocks( + formik.values.stocks?.map((_, idx) => idx) ?? + [] + ); + } else { + setSelectedStocks([]); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + + Persediaan + * - PenambahanPenguranganJumlah PakaiCatatanAction
PenambahanPenguranganJumlah PakaiCatatanAction
- ) => { - if (e.target.checked) { - setSelectedStocks([...selectedStocks, idx]); - } else { - setSelectedStocks( - selectedStocks.filter((i) => i !== idx), - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - + {formik.values.stocks?.map((stock, idx) => ( +
+ ) => { + if (e.target.checked) { + setSelectedStocks([...selectedStocks, idx]); + } else { + setSelectedStocks( + selectedStocks.filter((i) => i !== idx), + ); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + + + 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', + 'product_warehouse_id', + idx + ).isError + } + errorMessage={ + isRepeaterInputError( + 'stocks', + 'product_warehouse_id', + idx + ).errorMessage + } + className={{ + wrapper: 'w-full min-w-48', + }} + isSearchable + /> + + + + + + + - - +
+ +
- - - - - - -
- -
-
@@ -1063,200 +1145,200 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - {type !== 'detail' && ( - - )} + + {type !== 'detail' && ( + )} + - + - + - - {type !== 'detail' && } - + + + {type !== 'detail' && } + - {formik.values.depletions?.map((depletion, idx) => ( - - {type !== 'detail' && ( - - )} - + {type !== 'detail' && ( + + )} + + required + name={`depletions.${idx}.product_warehouse_id`} + type='number' + value={ + depletion.product_warehouse_id?.toString() || '' + } + onChange={formik.handleChange} + onBlur={formik.handleBlur} + isError={ + isRepeaterInputError( + 'depletions', + 'product_warehouse_id', + idx + ).isError + } + errorMessage={ + isRepeaterInputError( + 'depletions', + 'product_warehouse_id', + idx + ).errorMessage + } + readOnly={type === 'detail'} + className={{ + wrapper: 'w-full min-w-32', + }} + placeholder='Product Warehouse ID' + /> + + + + + {type !== 'detail' && ( - - - {type !== 'detail' && ( - - )} - - ))} + )} + + ))}
- 0 - } - onChange={(e: React.ChangeEvent) => { - if (e.target.checked) { - setSelectedDepletions( - formik.values.depletions?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedDepletions([]); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> -
- Product Warehouse ID - + 0 + } + onChange={(e: React.ChangeEvent) => { + if (e.target.checked) { + setSelectedDepletions( + formik.values.depletions?.map( + (_, idx) => idx + ) ?? [] + ); + } else { + setSelectedDepletions([]); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + + Product Warehouse ID + * - - Kondisi - + + Kondisi + * - - Total - + + Total + * - CatatanAction
CatatanAction
- ) => { - if (e.target.checked) { - setSelectedDepletions([ - ...selectedDepletions, - idx, - ]); - } else { - setSelectedDepletions( - selectedDepletions.filter((i) => i !== idx), - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - + {formik.values.depletions?.map((depletion, idx) => ( +
+ ) => { + if (e.target.checked) { + setSelectedDepletions([ + ...selectedDepletions, + idx, + ]); + } else { + setSelectedDepletions( + selectedDepletions.filter((i) => i !== idx), + ); + } + }} + classNames={{ + wrapper: 'flex justify-center', + checkbox: 'checkbox checkbox-sm', + }} + /> + - + + + + + + - +
+ +
- - - - -
- -
-