mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
feat(FE-170,175): integrate ProjectFlockKandang API and enhance RecordingForm with detailed project flock and kandang options
This commit is contained in:
@@ -11,7 +11,10 @@ import NumberInput from '@/components/input/NumberInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import { RecordingApi } from '@/services/api/production';
|
||||
import {
|
||||
ProjectFlockKandangApi,
|
||||
RecordingApi,
|
||||
} from '@/services/api/production';
|
||||
import {
|
||||
CreateGrowingRecordingPayload,
|
||||
CreateLayingRecordingPayload,
|
||||
@@ -36,6 +39,7 @@ import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||
import { cn, formatDate } from '@/lib/helper';
|
||||
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
|
||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -175,6 +179,26 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
? projectFlockKandangLookupData.data
|
||||
: undefined;
|
||||
|
||||
const projectFlockKandangDetailUrl = useMemo(() => {
|
||||
if (type === 'add' || !initialValues?.project_flock_kandang_id) return null;
|
||||
return `${ProjectFlockKandangApi.basePath}/${initialValues.project_flock_kandang_id}`;
|
||||
}, [type, initialValues?.project_flock_kandang_id]);
|
||||
|
||||
const { data: projectFlockKandangDetailData } = useSWR(
|
||||
projectFlockKandangDetailUrl,
|
||||
projectFlockKandangDetailUrl
|
||||
? () =>
|
||||
ProjectFlockKandangApi.getAllFetcher(
|
||||
projectFlockKandangDetailUrl
|
||||
) as Promise<BaseApiResponse<ProjectFlockKandang>>
|
||||
: null
|
||||
);
|
||||
|
||||
const projectFlockKandangDetail =
|
||||
projectFlockKandangDetailData?.status === 'success'
|
||||
? projectFlockKandangDetailData.data
|
||||
: undefined;
|
||||
|
||||
const stockProductsUrl = useMemo(() => {
|
||||
if (!selectedLocation) return null;
|
||||
const params = new URLSearchParams({
|
||||
@@ -228,40 +252,94 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
|
||||
// ===== DATA PROCESSING =====
|
||||
const locationOptions = useMemo(() => {
|
||||
if (!isResponseSuccess(locations)) return [];
|
||||
return (
|
||||
locations?.data.map((location) => ({
|
||||
value: location.id,
|
||||
label: location.name,
|
||||
})) || []
|
||||
);
|
||||
}, [locations]);
|
||||
let options: OptionType[] = [];
|
||||
|
||||
if (isResponseSuccess(locations)) {
|
||||
options = options.concat(
|
||||
locations?.data.map((location) => ({
|
||||
value: location.id,
|
||||
label: location.name,
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
|
||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||
const currentLocation = projectFlockKandangDetail.project_flock.location;
|
||||
if (
|
||||
currentLocation &&
|
||||
!options.find((opt) => opt.value === currentLocation.id)
|
||||
) {
|
||||
options.push({
|
||||
value: currentLocation.id,
|
||||
label: currentLocation.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [locations, projectFlockKandangDetail, type]);
|
||||
|
||||
const projectFlockOptions = useMemo(() => {
|
||||
if (!isResponseSuccess(projectFlocks)) return [];
|
||||
return (
|
||||
projectFlocks?.data.map((projectFlock) => ({
|
||||
value: projectFlock.id,
|
||||
label: projectFlock.flock_name,
|
||||
})) || []
|
||||
);
|
||||
}, [projectFlocks]);
|
||||
let options: OptionType[] = [];
|
||||
|
||||
if (isResponseSuccess(projectFlocks)) {
|
||||
options = options.concat(
|
||||
projectFlocks?.data.map((projectFlock) => ({
|
||||
value: projectFlock.id,
|
||||
label: projectFlock.flock_name,
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
|
||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||
const currentProjectFlock = projectFlockKandangDetail.project_flock;
|
||||
if (
|
||||
currentProjectFlock &&
|
||||
!options.find((opt) => opt.value === currentProjectFlock.id)
|
||||
) {
|
||||
options.push({
|
||||
value: currentProjectFlock.id,
|
||||
label: currentProjectFlock.flock_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [projectFlocks, projectFlockKandangDetail, type]);
|
||||
|
||||
const kandangOptions = useMemo(() => {
|
||||
if (!selectedProjectFlock || !isResponseSuccess(projectFlocks)) return [];
|
||||
let options: OptionType[] = [];
|
||||
|
||||
const selectedProjectFlockData = projectFlocks.data.find(
|
||||
(pf) => pf.id === selectedProjectFlock.value
|
||||
);
|
||||
if (selectedProjectFlock && isResponseSuccess(projectFlocks)) {
|
||||
const selectedProjectFlockData = projectFlocks.data.find(
|
||||
(pf) => pf.id === selectedProjectFlock.value
|
||||
);
|
||||
|
||||
if (!selectedProjectFlockData || !selectedProjectFlockData.kandangs)
|
||||
return [];
|
||||
if (selectedProjectFlockData?.kandangs) {
|
||||
options = options.concat(
|
||||
selectedProjectFlockData.kandangs.map((kandang: Kandang) => ({
|
||||
value: kandang.id,
|
||||
label: kandang.name,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return selectedProjectFlockData.kandangs.map((kandang: Kandang) => ({
|
||||
value: kandang.id,
|
||||
label: kandang.name,
|
||||
}));
|
||||
}, [selectedProjectFlock, projectFlocks]);
|
||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||
const currentKandang = projectFlockKandangDetail.kandang;
|
||||
if (
|
||||
currentKandang &&
|
||||
!options.find((opt) => opt.value === currentKandang.id)
|
||||
) {
|
||||
options.push({
|
||||
value: currentKandang.id,
|
||||
label: currentKandang.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [selectedProjectFlock, projectFlocks, projectFlockKandangDetail, type]);
|
||||
|
||||
const recordedProjectFlockKandangIds = useMemo(() => {
|
||||
if (!isResponseSuccess(existingRecordings)) return new Set<number>();
|
||||
@@ -408,16 +486,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
|
||||
const isLayingCategory =
|
||||
initialValues?.project_flock_category === 'LAYING' ||
|
||||
projectFlockKandangLookup?.project_flock?.category === 'LAYING';
|
||||
projectFlockKandangLookup?.project_flock?.category === 'LAYING' ||
|
||||
projectFlockKandangDetail?.project_flock?.category === 'LAYING';
|
||||
|
||||
const formikInitialValues = useMemo(() => {
|
||||
let baseValues;
|
||||
if (isLayingCategory) {
|
||||
return getRecordingLayingFormInitialValues(
|
||||
baseValues = getRecordingLayingFormInitialValues(
|
||||
initialValues
|
||||
) as RecordingLayingFormValues;
|
||||
} else {
|
||||
baseValues = getRecordingGrowingFormInitialValues(initialValues);
|
||||
}
|
||||
return getRecordingGrowingFormInitialValues(initialValues);
|
||||
}, [initialValues, isLayingCategory]);
|
||||
|
||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||
baseValues.project_flock_kandang = {
|
||||
value: projectFlockKandangDetail.project_flock.id,
|
||||
label: projectFlockKandangDetail.project_flock.flock_name,
|
||||
};
|
||||
}
|
||||
|
||||
return baseValues;
|
||||
}, [initialValues, isLayingCategory, projectFlockKandangDetail, type]);
|
||||
|
||||
const formik = useFormik<
|
||||
RecordingGrowingFormValues | RecordingLayingFormValues
|
||||
@@ -756,7 +846,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
|
||||
formik.setFieldValue('project_flock_kandang', {
|
||||
value: projectFlockKandangId,
|
||||
label: `${selectedProjectFlock?.label || ''} - ${selectedKandang?.label || ''}`,
|
||||
label: projectFlockKandangLookup
|
||||
? `${projectFlockKandangLookup.project_flock.flock_name} - ${projectFlockKandangLookup.kandang.name}`
|
||||
: `${selectedProjectFlock?.label || ''} - ${selectedKandang?.label || ''}`,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
@@ -767,6 +859,47 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
recordedProjectFlockKandangIds,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) {
|
||||
const location = projectFlockKandangDetail.project_flock.location;
|
||||
const projectFlock = projectFlockKandangDetail.project_flock;
|
||||
const kandang = projectFlockKandangDetail.kandang;
|
||||
|
||||
if (location) {
|
||||
const locationOption = {
|
||||
value: location.id,
|
||||
label: location.name,
|
||||
};
|
||||
setSelectedLocation(locationOption);
|
||||
|
||||
if (projectFlock) {
|
||||
const projectFlockOption = {
|
||||
value: projectFlock.id,
|
||||
label: projectFlock.flock_name,
|
||||
};
|
||||
setSelectedProjectFlock(projectFlockOption);
|
||||
|
||||
if (kandang) {
|
||||
const kandangOption = {
|
||||
value: kandang.id,
|
||||
label: kandang.name,
|
||||
};
|
||||
setSelectedKandang(kandangOption);
|
||||
|
||||
formik.setFieldValue(
|
||||
'project_flock_kandang_id',
|
||||
projectFlockKandangDetail.id
|
||||
);
|
||||
formik.setFieldValue('project_flock_kandang', {
|
||||
value: projectFlockKandangDetail.id,
|
||||
label: `${projectFlock.flock_name} - ${kandang.name}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [projectFlockKandangDetail, type, projectFlockOptions]);
|
||||
|
||||
const approveHandler = async () => {
|
||||
setIsApproveLoading(true);
|
||||
|
||||
@@ -1041,7 +1174,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
formik.setFieldValue('eggs', [{ product_warehouse_id: 0, qty: '' }]);
|
||||
}
|
||||
}
|
||||
}, [isLayingCategory, type, formik]);
|
||||
}, [isLayingCategory, type]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLayingCategory) {
|
||||
@@ -1120,73 +1253,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
{type === 'detail' && 'Detail Recording'}
|
||||
</h1>
|
||||
</header>
|
||||
{/* Project Flock Info Card */}
|
||||
{projectFlockKandangLookup && (
|
||||
<div className='flex items-center gap-2 mb-4'>
|
||||
{/* Form Steps for LAYING Category */}
|
||||
{formSteps && (
|
||||
<div className='flex-1 mt-4'>
|
||||
<div className='w-full'>
|
||||
<ul className='steps w-full'>
|
||||
{formSteps.map((step, idx) => (
|
||||
<StepItem
|
||||
key={idx}
|
||||
color={
|
||||
step.isCompleted
|
||||
? 'success'
|
||||
: step.isCurrent
|
||||
? 'primary'
|
||||
: undefined
|
||||
}
|
||||
icon={
|
||||
step.isCompleted ? (
|
||||
<Icon
|
||||
icon='material-symbols:check-rounded'
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
) : (
|
||||
idx + 1
|
||||
)
|
||||
}
|
||||
>
|
||||
{step.name}
|
||||
</StepItem>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Action buttons for multi-form navigation */}
|
||||
{type === 'detail' && (
|
||||
<div className='mt-4 flex gap-2'>
|
||||
<Button
|
||||
type='button'
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/production/recording/grading/add?recording_id=${initialValues?.id}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
Lanjut ke Grading
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
color='success'
|
||||
onClick={() => {
|
||||
toast.success(
|
||||
'Semua form akan disubmit untuk approval'
|
||||
);
|
||||
}}
|
||||
>
|
||||
Submit Semua Form
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={formik.handleSubmit}
|
||||
@@ -1218,7 +1284,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
key={`project-flock-select-${selectedProjectFlock?.value || 'default'}`}
|
||||
key={`project-flock-select-${selectedProjectFlock?.value || 'default'}-${projectFlockOptions.length}`}
|
||||
required
|
||||
label='Project Flock'
|
||||
value={selectedProjectFlock}
|
||||
@@ -1237,7 +1303,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
key={`kandang-select-${projectFlockKandangLookup?.project_flock_kandang_id || 'default'}`}
|
||||
key={`kandang-select-${selectedKandang?.value || 'default'}-${kandangOptions.length}`}
|
||||
required
|
||||
label='Kandang'
|
||||
value={selectedKandang}
|
||||
@@ -1251,9 +1317,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
: 'Pilih Project Flock terlebih dahulu'
|
||||
}
|
||||
isClearable
|
||||
isSearchable={false}
|
||||
isSearchable
|
||||
startAdornment={
|
||||
projectFlockKandangLookup
|
||||
projectFlockKandangLookup || projectFlockKandangDetail
|
||||
? getProjectFlockBadgeAdornment()
|
||||
: undefined
|
||||
}
|
||||
@@ -1263,54 +1329,100 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Recording Info for Detail View */}
|
||||
{type === 'detail' && initialValues && (
|
||||
<Card
|
||||
title='Informasi Recording'
|
||||
className={{
|
||||
wrapper: 'w-full mb-4 shadow',
|
||||
body: 'flex flex-col gap-4',
|
||||
}}
|
||||
>
|
||||
<div className='grid grid-cols-2 md:grid-cols-4 gap-4'>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Recording ID</span>
|
||||
<p className='font-semibold'>#{initialValues.id}</p>
|
||||
{/* Combined Info Card for Detail View */}
|
||||
{type === 'detail' &&
|
||||
initialValues &&
|
||||
(projectFlockKandangLookup || projectFlockKandangDetail) && (
|
||||
<Card
|
||||
title='Informasi Recording'
|
||||
className={{
|
||||
wrapper: 'w-full mb-4 shadow',
|
||||
body: 'flex flex-col gap-4',
|
||||
}}
|
||||
>
|
||||
<div className='grid grid-cols-2 md:grid-cols-4 gap-4'>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Recording ID</span>
|
||||
<p className='font-semibold'>#{initialValues.id}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Lokasi</span>
|
||||
<p className='font-semibold'>
|
||||
{projectFlockKandangLookup?.project_flock?.location
|
||||
?.name ||
|
||||
projectFlockKandangDetail?.project_flock?.location
|
||||
?.name ||
|
||||
'-'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Project Flock</span>
|
||||
<p className='font-semibold'>
|
||||
{projectFlockKandangLookup?.project_flock?.flock_name ||
|
||||
projectFlockKandangDetail?.project_flock?.flock_name ||
|
||||
'-'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Kandang</span>
|
||||
<p className='font-semibold'>
|
||||
{projectFlockKandangLookup?.kandang?.name ||
|
||||
projectFlockKandangDetail?.kandang?.name ||
|
||||
'-'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>
|
||||
Tanggal Recording
|
||||
</span>
|
||||
<p className='font-semibold'>
|
||||
{formatDate(
|
||||
initialValues.record_datetime || '',
|
||||
'DD MMMM YYYY'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Hari</span>
|
||||
<p className='font-semibold'>Hari ke-{initialValues.day}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Kategori</span>
|
||||
<p className='font-semibold'>
|
||||
<Badge
|
||||
variant='soft'
|
||||
color={
|
||||
initialValues.project_flock_category === 'LAYING'
|
||||
? 'info'
|
||||
: 'warning'
|
||||
}
|
||||
size='sm'
|
||||
>
|
||||
{initialValues.project_flock_category}
|
||||
</Badge>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Periode</span>
|
||||
<p className='font-semibold'>
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='neutral'
|
||||
size='sm'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||
}}
|
||||
>
|
||||
Periode{' '}
|
||||
{projectFlockKandangLookup?.project_flock?.period ||
|
||||
projectFlockKandangDetail?.project_flock?.period ||
|
||||
'-'}
|
||||
</Badge>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>
|
||||
Tanggal Recording
|
||||
</span>
|
||||
<p className='font-semibold'>
|
||||
{formatDate(
|
||||
initialValues.record_datetime || '',
|
||||
'DD MMMM YYYY'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Hari</span>
|
||||
<p className='font-semibold'>Hari ke-{initialValues.day}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-sm text-gray-600'>Kategori</span>
|
||||
<p className='font-semibold'>
|
||||
<Badge
|
||||
variant='soft'
|
||||
color={
|
||||
initialValues.project_flock_category === 'LAYING'
|
||||
? 'info'
|
||||
: 'warning'
|
||||
}
|
||||
size='sm'
|
||||
>
|
||||
{initialValues.project_flock_category}
|
||||
</Badge>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Body Weights Table */}
|
||||
<Card
|
||||
|
||||
Reference in New Issue
Block a user