mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
feat(FE-170,174): implement project flock kandang selection and validation in daily recording form
This commit is contained in:
@@ -29,12 +29,16 @@ 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 { PeriodFlock } from '@/types/api/production/project-flock';
|
||||
import {
|
||||
PeriodFlock,
|
||||
ProjectFlockKandangLookup,
|
||||
} from '@/types/api/production/project-flock';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import Badge from '@/components/Badge';
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
|
||||
interface RecordingFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -46,9 +50,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const [selectedStocks, setSelectedStocks] = useState<number[]>([]);
|
||||
const [selectedDepletions, setSelectedDepletions] = useState<number[]>([]);
|
||||
|
||||
const [editingAverageIndex, setEditingAverageIndex] = useState<number | null>(
|
||||
null
|
||||
);
|
||||
const [editingAverageIndex] = useState<number | null>(null);
|
||||
const [manuallyEditedRows, setManuallyEditedRows] = useState<Set<number>>(
|
||||
new Set()
|
||||
);
|
||||
@@ -58,6 +60,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
null
|
||||
);
|
||||
const [projectFlockSearchValue, setProjectFlockSearchValue] = useState('');
|
||||
const [selectedProjectFlock, setSelectedProjectFlock] =
|
||||
useState<OptionType | null>(null);
|
||||
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||
@@ -85,6 +92,30 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
ProjectFlockApi.getAllFetcher
|
||||
);
|
||||
|
||||
const projectFlockKandangLookupUrl = useMemo(() => {
|
||||
if (!selectedProjectFlock || !selectedKandang) return null;
|
||||
const params = new URLSearchParams({
|
||||
project_flock_id: selectedProjectFlock.value.toString(),
|
||||
kandang_id: selectedKandang.value.toString(),
|
||||
});
|
||||
return `${ProjectFlockApi.basePath}/kandangs/lookup?${params.toString()}`;
|
||||
}, [selectedProjectFlock, selectedKandang]);
|
||||
|
||||
const { data: projectFlockKandangLookupData } = useSWR(
|
||||
projectFlockKandangLookupUrl,
|
||||
projectFlockKandangLookupUrl
|
||||
? () =>
|
||||
ProjectFlockApi.getAllFetcher(
|
||||
projectFlockKandangLookupUrl
|
||||
) as Promise<BaseApiResponse<ProjectFlockKandangLookup>>
|
||||
: null
|
||||
);
|
||||
|
||||
const projectFlockKandangLookup =
|
||||
projectFlockKandangLookupData?.status === 'success'
|
||||
? projectFlockKandangLookupData.data
|
||||
: undefined;
|
||||
|
||||
const stockProductsUrl = useMemo(() => {
|
||||
if (!selectedLocation) return null;
|
||||
const params = new URLSearchParams({
|
||||
@@ -199,25 +230,47 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
);
|
||||
}, [locations]);
|
||||
|
||||
const projectFlockKandangOptions = useMemo(() => {
|
||||
const projectFlockOptions = useMemo(() => {
|
||||
if (!isResponseSuccess(projectFlocks)) return [];
|
||||
return (
|
||||
projectFlocks?.data.map((projectFlock) => ({
|
||||
value: projectFlock.id,
|
||||
label: projectFlock.flock.name,
|
||||
})) || []
|
||||
);
|
||||
}, [projectFlocks]);
|
||||
|
||||
const options: OptionType[] = [];
|
||||
projectFlocks?.data.forEach((projectFlock) => {
|
||||
projectFlock.kandangs.forEach((kandang) => {
|
||||
const isAlreadyRecorded = recordedProjectFlockIds.has(projectFlock.id);
|
||||
const label = isAlreadyRecorded
|
||||
? `${projectFlock.flock.name} - ${kandang.name} (Sudah Direcord)`
|
||||
: `${projectFlock.flock.name} - ${kandang.name}`;
|
||||
const kandangOptions = useMemo(() => {
|
||||
if (!selectedProjectFlock || !isResponseSuccess(projectFlocks)) return [];
|
||||
|
||||
options.push({
|
||||
value: projectFlock.id,
|
||||
label: label,
|
||||
});
|
||||
});
|
||||
const selectedProjectFlockData = projectFlocks.data.find(
|
||||
(pf) => pf.id === selectedProjectFlock.value
|
||||
);
|
||||
|
||||
if (!selectedProjectFlockData || !selectedProjectFlockData.kandangs)
|
||||
return [];
|
||||
|
||||
return selectedProjectFlockData.kandangs.map((kandang: Kandang) => ({
|
||||
value: kandang.id,
|
||||
label: kandang.name,
|
||||
}));
|
||||
}, [selectedProjectFlock, projectFlocks]);
|
||||
|
||||
const recordedProjectFlockKandangIds = useMemo(() => {
|
||||
if (!isResponseSuccess(existingRecordings)) return new Set<number>();
|
||||
|
||||
const todayRecordings = existingRecordings?.data || [];
|
||||
const recordedIds = new Set<number>();
|
||||
|
||||
todayRecordings.forEach((recording) => {
|
||||
const recordingDate = recording.record_datetime?.split('T')[0];
|
||||
if (recordingDate === today) {
|
||||
recordedIds.add(recording.project_flock_kandangs_id);
|
||||
}
|
||||
});
|
||||
return options;
|
||||
}, [projectFlocks, recordedProjectFlockIds, type]);
|
||||
|
||||
return recordedIds;
|
||||
}, [existingRecordings, today]);
|
||||
|
||||
const unifiedStockProducts = useMemo(() => {
|
||||
const options: OptionType[] = [];
|
||||
@@ -432,39 +485,38 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
[formik.values.stocks, getAvailableStock, type]
|
||||
);
|
||||
|
||||
const getProjectFlockBadgeAdornment = useCallback(
|
||||
(projectFlockId: number) => {
|
||||
if (!isResponseSuccess(projectFlocks)) return null;
|
||||
const getProjectFlockBadgeAdornment = useCallback(() => {
|
||||
if (!isResponseSuccess(projectFlocks) || !projectFlockKandangLookup)
|
||||
return null;
|
||||
|
||||
const projectFlock = projectFlocks.data.find(
|
||||
(pf) => pf.id === projectFlockId
|
||||
);
|
||||
if (!projectFlock) return null;
|
||||
const isAlreadyRecorded = recordedProjectFlockKandangIds.has(
|
||||
projectFlockKandangLookup.id
|
||||
);
|
||||
let color: 'neutral' | 'success' | 'warning' | 'error' = 'neutral';
|
||||
|
||||
const isAlreadyRecorded = recordedProjectFlockIds.has(projectFlockId);
|
||||
let color: 'neutral' | 'success' | 'warning' | 'error' = 'neutral';
|
||||
if (isAlreadyRecorded) {
|
||||
color = 'warning';
|
||||
} else {
|
||||
color = 'success';
|
||||
}
|
||||
|
||||
if (isAlreadyRecorded) {
|
||||
color = 'warning';
|
||||
} else {
|
||||
color = 'success';
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color={color}
|
||||
size='sm'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||
}}
|
||||
>
|
||||
Periode {projectFlock.period}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
[projectFlocks, recordedProjectFlockIds]
|
||||
);
|
||||
return (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color={color}
|
||||
size='sm'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||
}}
|
||||
>
|
||||
Periode {projectFlockKandangLookup.project_flock?.period}
|
||||
</Badge>
|
||||
);
|
||||
}, [
|
||||
projectFlocks,
|
||||
recordedProjectFlockKandangIds,
|
||||
projectFlockKandangLookup,
|
||||
]);
|
||||
|
||||
const getProductFlagBadgeAdornment = useCallback(
|
||||
(productWarehouseId: number) => {
|
||||
@@ -563,31 +615,53 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
// ===== EVENT HANDLERS =====
|
||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedLocation(val as OptionType);
|
||||
setSelectedProjectFlock(null);
|
||||
setSelectedKandang(null);
|
||||
formik.setFieldValue('project_flock_kandang', null);
|
||||
formik.setFieldValue('project_flock_kandangs_id', 0);
|
||||
};
|
||||
|
||||
const projectFlockKandangChangeHandler = (
|
||||
val: OptionType | OptionType[] | null
|
||||
) => {
|
||||
if (
|
||||
type === 'add' &&
|
||||
val &&
|
||||
recordedProjectFlockIds.has((val as OptionType).value as number)
|
||||
) {
|
||||
toast.error('Project Flock ini sudah direcord hari ini!');
|
||||
return;
|
||||
}
|
||||
|
||||
formik.setFieldTouched('project_flock_kandang', true);
|
||||
formik.setFieldValue('project_flock_kandang', val);
|
||||
formik.setFieldTouched('project_flock_kandangs_id', true);
|
||||
formik.setFieldValue(
|
||||
'project_flock_kandangs_id',
|
||||
(val as OptionType)?.value || 0
|
||||
);
|
||||
const projectFlockChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedProjectFlock(val as OptionType);
|
||||
setSelectedKandang(null);
|
||||
formik.setFieldValue('project_flock_kandang', null);
|
||||
formik.setFieldValue('project_flock_kandangs_id', 0);
|
||||
};
|
||||
|
||||
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedKandang(val as OptionType);
|
||||
formik.setFieldTouched('project_flock_kandang', true);
|
||||
formik.setFieldTouched('project_flock_kandangs_id', true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (projectFlockKandangLookup?.project_flock_kandang_id) {
|
||||
const projectFlockKandangId =
|
||||
projectFlockKandangLookup.project_flock_kandang_id;
|
||||
|
||||
if (
|
||||
type === 'add' &&
|
||||
recordedProjectFlockKandangIds.has(projectFlockKandangId)
|
||||
) {
|
||||
toast.error('Project Flock Kandang ini sudah direcord hari ini!');
|
||||
return;
|
||||
}
|
||||
|
||||
formik.setFieldValue('project_flock_kandangs_id', projectFlockKandangId);
|
||||
|
||||
formik.setFieldValue('project_flock_kandang', {
|
||||
value: projectFlockKandangId,
|
||||
label: `${selectedProjectFlock?.label || ''} - ${selectedKandang?.label || ''}`,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
projectFlockKandangLookup,
|
||||
selectedProjectFlock,
|
||||
selectedKandang,
|
||||
type,
|
||||
recordedProjectFlockKandangIds,
|
||||
]);
|
||||
|
||||
const approveHandler = async () => {
|
||||
setIsApproveLoading(true);
|
||||
|
||||
@@ -868,6 +942,21 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
title='Recording'
|
||||
backUrl='/production/recording'
|
||||
/>
|
||||
|
||||
{/* Project Flock Info Card */}
|
||||
{projectFlockKandangLookup && (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='info'
|
||||
size='md'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||
}}
|
||||
>
|
||||
{projectFlockKandangLookup.project_flock.category}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={formik.handleSubmit}
|
||||
className='w-full mt-8 flex flex-col gap-6'
|
||||
@@ -884,58 +973,78 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
className={
|
||||
type === 'detail'
|
||||
? 'flex flex-col gap-6'
|
||||
: 'grid grid-cols-1 md:grid-cols-2 gap-6'
|
||||
: 'grid grid-cols-3 gap-4'
|
||||
}
|
||||
>
|
||||
{type === 'detail' ? null : (
|
||||
<SelectInput
|
||||
required
|
||||
label='Lokasi'
|
||||
value={selectedLocation}
|
||||
onChange={locationChangeHandler}
|
||||
options={locationOptions}
|
||||
onInputChange={setLocationSearchValue}
|
||||
isLoading={isLoadingLocations}
|
||||
placeholder='Pilih Lokasi'
|
||||
isClearable
|
||||
isSearchable
|
||||
/>
|
||||
<>
|
||||
<SelectInput
|
||||
required
|
||||
label='Lokasi'
|
||||
value={selectedLocation}
|
||||
onChange={locationChangeHandler}
|
||||
options={locationOptions}
|
||||
onInputChange={setLocationSearchValue}
|
||||
isLoading={isLoadingLocations}
|
||||
placeholder='Pilih Lokasi'
|
||||
isClearable
|
||||
isSearchable
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
required
|
||||
label='Project Flock'
|
||||
value={selectedProjectFlock}
|
||||
onChange={projectFlockChangeHandler}
|
||||
options={projectFlockOptions}
|
||||
onInputChange={setProjectFlockSearchValue}
|
||||
isLoading={isLoadingProjectFlocks}
|
||||
isDisabled={!selectedLocation}
|
||||
placeholder={
|
||||
selectedLocation
|
||||
? 'Pilih Project Flock'
|
||||
: 'Pilih Lokasi terlebih dahulu'
|
||||
}
|
||||
isClearable
|
||||
isSearchable
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
required
|
||||
label='Kandang'
|
||||
value={selectedKandang}
|
||||
onChange={kandangChangeHandler}
|
||||
options={kandangOptions}
|
||||
isLoading={false}
|
||||
isDisabled={!selectedProjectFlock}
|
||||
placeholder={
|
||||
selectedProjectFlock
|
||||
? 'Pilih Kandang'
|
||||
: 'Pilih Project Flock terlebih dahulu'
|
||||
}
|
||||
isClearable
|
||||
isSearchable={false}
|
||||
startAdornment={
|
||||
projectFlockKandangLookup
|
||||
? getProjectFlockBadgeAdornment()
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<SelectInput
|
||||
required
|
||||
key={`project-flock-${formik.values.project_flock_kandangs_id}`}
|
||||
label='Project Flock'
|
||||
value={formik.values.project_flock_kandang ?? undefined}
|
||||
onChange={projectFlockKandangChangeHandler}
|
||||
options={projectFlockKandangOptions}
|
||||
onInputChange={setProjectFlockSearchValue}
|
||||
isLoading={isLoadingProjectFlocks}
|
||||
isError={
|
||||
formik.touched.project_flock_kandangs_id &&
|
||||
Boolean(formik.errors.project_flock_kandangs_id)
|
||||
}
|
||||
errorMessage={
|
||||
formik.errors.project_flock_kandangs_id as string
|
||||
}
|
||||
isDisabled={type === 'detail' || !selectedLocation}
|
||||
placeholder={
|
||||
selectedLocation
|
||||
? 'Pilih Project Flock - Kandang'
|
||||
: 'Pilih Lokasi terlebih dahulu'
|
||||
}
|
||||
isClearable
|
||||
isSearchable
|
||||
startAdornment={
|
||||
formik.values.project_flock_kandangs_id
|
||||
? getProjectFlockBadgeAdornment(
|
||||
formik.values.project_flock_kandangs_id
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{type === 'detail' && formik.values.project_flock_kandang && (
|
||||
<div className='form-control'>
|
||||
<label className='label'>
|
||||
<span className='label-text font-semibold'>
|
||||
Project Flock - Kandang
|
||||
</span>
|
||||
</label>
|
||||
<div className='input input-bordered bg-gray-50'>
|
||||
{formik.values.project_flock_kandang.label}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
|
||||
+10
@@ -47,3 +47,13 @@ export type ProjectFlockApprovalPayload = {
|
||||
action: 'APPROVED' | 'REJECTED';
|
||||
approvable_ids: number[];
|
||||
};
|
||||
|
||||
export type ProjectFlockKandangLookup = {
|
||||
id: number;
|
||||
project_flock_kandang_id: number;
|
||||
project_flock_id: number;
|
||||
kandang_id: number;
|
||||
kandang: Kandang;
|
||||
project_flock: ProjectFlock;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user