fix(FE-88-89) adjust category flock dengan API backend & set disabled input period

This commit is contained in:
randy-ar
2025-10-21 10:14:17 +07:00
parent 9964e1797a
commit 1672705464
10 changed files with 262 additions and 42 deletions
@@ -0,0 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
const Layout = ({
children
}: Readonly<{
children: React.ReactNode
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
export default Layout;
@@ -0,0 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
const Layout = ({
children
}: Readonly<{
children: React.ReactNode
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
export default Layout;
@@ -0,0 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
const Layout = ({
children
}: Readonly<{
children: React.ReactNode
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
export default Layout;
@@ -0,0 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
const Layout = ({
children
}: Readonly<{
children: React.ReactNode
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
export default Layout;
@@ -0,0 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
const Layout = ({
children
}: Readonly<{
children: React.ReactNode
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
export default Layout;
@@ -3,6 +3,7 @@
import Button from '@/components/Button'; import Button from '@/components/Button';
import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import TextInput from '@/components/input/TextInput';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table'; import Table from '@/components/Table';
@@ -11,6 +12,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data';
import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlockApi } from '@/services/api/production';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Kandang } from '@/types/api/master-data/kandang'; import { Kandang } from '@/types/api/master-data/kandang';
@@ -92,12 +94,33 @@ const ProjectFlockTable = () => {
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: '', search: '',
areaFilter: '',
locationFilter: '',
kandangFilter: '',
periodFilter: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
search: 'search',
areaFilter: 'area_id',
locationFilter: 'location_id',
kandangFilter: 'kandang_id',
periodFilter: 'period',
}, },
}); });
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null
);
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(
null
);
const [periodInputValue, setPeriodInputValue] = useState<number | null>(null);
// Fetch Data // Fetch Data
const { const {
@@ -109,6 +132,59 @@ const ProjectFlockTable = () => {
ProjectFlockApi.getAllFetcher ProjectFlockApi.getAllFetcher
); );
const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({
search: areaSelectInputValue,
limit: '100',
}).toString()}`;
const {
data: areas,
isLoading: isLoadingAreas,
mutate: refreshAreas,
} = useSWR(areaUrl, AreaApi.getAllFetcher);
const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({
search: locationSelectInputValue,
area_id: selectedArea != null ? selectedArea.value.toString() : '',
limit: '100',
}).toString()}`;
const {
data: locations,
isLoading: isLoadingLocations,
mutate: refreshLocations,
} = useSWR(locationUrl, LocationApi.getAllFetcher);
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
search: kandangSelectInputValue,
location_id:
selectedLocation != null ? selectedLocation.value.toString() : '',
limit: '100',
}).toString()}`;
const {
data: kandangs,
isLoading: isLoadingKandang,
mutate: refreshKandang,
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
// Data to Options Mapping
const optionsArea = isResponseSuccess(areas)
? areas?.data.map((area) => ({
value: area.id,
label: area.name,
}))
: [];
const optionsKandang = isResponseSuccess(kandangs)
? kandangs?.data.map((kandang) => ({
value: kandang.id,
label: kandang.name,
}))
: [];
const optionsLocation = isResponseSuccess(locations)
? locations?.data.map((location) => ({
value: location.id,
label: location.name,
}))
: [];
// State // State
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const [selectedProjectFlock, setSelectedProjectFlock] = const [selectedProjectFlock, setSelectedProjectFlock] =
@@ -170,21 +246,23 @@ const ProjectFlockTable = () => {
header: 'FCR', header: 'FCR',
}, },
{ {
accessorKey: 'product_category.name', accessorKey: 'category',
header: 'Kategori Produk', header: 'Kategori',
}, },
{ {
header: 'Kandang', header: 'Kandang',
cell: (props) => { cell: (props) => {
const kandang = props.row.original.kandangs; const kandang = props.row.original.kandangs;
const kandangNames = kandang.map((k: Kandang) => k.name); if (kandang) {
console.log('kandang'); const kandangNames = kandang.map((k: Kandang) => k.name);
console.log(kandang); return (
return ( <div>
<div> {kandangNames.length > 0 ? kandangNames.join(', ') : 'Tidak ada'}
{kandangNames.length > 0 ? kandangNames.join(', ') : 'Tidak ada'} </div>
</div> );
); }else{
return '-';
}
}, },
}, },
{ {
@@ -341,7 +419,60 @@ const ProjectFlockTable = () => {
/> />
</div> </div>
</div> </div>
<div className='hidden sm:flex flex-row justify-end'> <div className='hidden sm:flex flex-row justify-end gap-6 w-full'>
<SelectInput
label='Area'
options={optionsArea}
isLoading={isLoadingAreas}
value={selectedArea}
onChange={(val) => {
setSelectedArea(val as OptionType);
updateFilter(
'areaFilter',
(val as OptionType)?.value.toString()
);
}}
isClearable
/>
<SelectInput
label='Lokasi'
options={optionsLocation}
isLoading={isLoadingLocations}
value={selectedLocation}
onChange={(val) => {
setSelectedLocation(val as OptionType);
updateFilter(
'locationFilter',
(val as OptionType)?.value.toString()
);
}}
isClearable
/>
<SelectInput
label='Kandang'
options={optionsKandang}
isLoading={isLoadingKandang}
value={selectedKandang}
onChange={(val) => {
setSelectedKandang(val as OptionType);
updateFilter(
'kandangFilter',
(val as OptionType)?.value.toString()
);
}}
isClearable
/>
<DebouncedTextInput
name='period'
type='number'
label='Periode'
placeholder='Masukan periode'
value={periodInputValue ?? ''}
onChange={(e) => {
setPeriodInputValue(parseInt(e.target.value));
updateFilter('periodFilter', e.target.value);
}}
/>
<SelectInput <SelectInput
label='Baris' label='Baris'
options={ROWS_OPTIONS} options={ROWS_OPTIONS}
@@ -390,6 +521,21 @@ const ProjectFlockTable = () => {
</div> </div>
</div> </div>
<ConfirmationModal
ref={deleteModal.ref}
type='error'
text={`Apakah anda yakin ingin menghapus data Project Flock ini (${selectedProjectFlock?.flock?.name})?`}
secondaryButton={{
text: 'Tidak',
}}
primaryButton={{
text: 'Ya',
color: 'error',
isLoading: isDeleteLoading,
onClick: confirmationModalDeleteClickHandler,
}}
/>
<ConfirmationModal <ConfirmationModal
ref={confirmModal.ref} ref={confirmModal.ref}
type='success' type='success'
@@ -20,14 +20,13 @@ export const ProjectFlockFormSchema = Yup.object({
.min(1, 'Area wajib diisi!') .min(1, 'Area wajib diisi!')
.required('Area wajib diisi!'), .required('Area wajib diisi!'),
//Product Category // Kategori
product_category: Yup.object({ category_option: Yup.object({
value: Yup.number().required('ID Kategori Produk wajib diisi!'), value: Yup.string().required('Nilai Kategori wajib diisi!'),
label: Yup.string().required('Nama Kategori Produk wajib diisi!'), label: Yup.string().required('Label Kategori wajib diisi!'),
}).nullable(), }).nullable(),
product_category_id: Yup.number() category: Yup.string().oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
.min(1, 'Kategori Produk wajib diisi!') .required('Kategori wajib diisi!'),
.required('Kategori Produk wajib diisi!'),
// FCR // FCR
fcr: Yup.object({ fcr: Yup.object({
@@ -34,6 +34,7 @@ import { ProjectFlockApi } from '@/services/api/production';
import { httpClient } from '@/services/http/client'; import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general'; import { BaseApiResponse } from '@/types/api/api-general';
import axios from 'axios'; import axios from 'axios';
import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
interface ProjectFlockFormProps { interface ProjectFlockFormProps {
formType?: 'add' | 'edit' | 'detail'; formType?: 'add' | 'edit' | 'detail';
@@ -202,7 +203,7 @@ const ProjectFlockForm = ({
const optionChangeHandler = ( const optionChangeHandler = (
val: OptionType | OptionType[] | null, val: OptionType | OptionType[] | null,
inputName: string inputName: string,
) => { ) => {
formik.setFieldValue(inputName, val); formik.setFieldValue(inputName, val);
formik.setFieldValue( formik.setFieldValue(
@@ -213,6 +214,12 @@ const ProjectFlockForm = ({
formik.setFieldTouched(`${inputName}_id`, true); formik.setFieldTouched(`${inputName}_id`, true);
}; };
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldValue('category', (val as OptionType)?.value);
formik.setFieldValue('category_option', val);
formik.setFieldTouched('category', true);
}
const kandangChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => { const kandangChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value, checked } = event.target; const { value, checked } = event.target;
if (checked) { if (checked) {
@@ -233,7 +240,7 @@ const ProjectFlockForm = ({
formik.setFieldValue( formik.setFieldValue(
'kandang_ids', 'kandang_ids',
optionsKandang optionsKandang
.filter((kandang) => kandang.status === 'NON_ACTIVE') .filter((kandang) => (kandang.status === 'NON_ACTIVE' || formik.values.kandang_ids.includes(kandang.id)))
.map((kandang) => kandang.id) .map((kandang) => kandang.id)
); );
} else { } else {
@@ -290,10 +297,10 @@ const ProjectFlockForm = ({
label: initialValues.area.name, label: initialValues.area.name,
} }
: null, : null,
product_category: initialValues?.product_category category_option: initialValues?.category
? { ? {
value: initialValues.product_category.id, value: initialValues.category,
label: initialValues.product_category.name, label: initialValues.category,
} }
: null, : null,
fcr: initialValues?.fcr fcr: initialValues?.fcr
@@ -310,11 +317,11 @@ const ProjectFlockForm = ({
: null, : null,
flock_id: initialValues?.flock?.id ?? 0, flock_id: initialValues?.flock?.id ?? 0,
area_id: initialValues?.area?.id ?? 0, area_id: initialValues?.area?.id ?? 0,
product_category_id: initialValues?.product_category?.id ?? 0, category: initialValues?.category,
fcr_id: initialValues?.fcr?.id ?? 0, fcr_id: initialValues?.fcr?.id ?? 0,
location_id: initialValues?.location?.id ?? 0, location_id: initialValues?.location?.id ?? 0,
period: initialValues?.period ?? '', period: initialValues?.period ?? '',
kandang_ids: [], kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id) ?? [],
}; };
}, [initialValues]); }, [initialValues]);
@@ -332,7 +339,7 @@ const ProjectFlockForm = ({
const payload: CreateProjectFlockPayload = { const payload: CreateProjectFlockPayload = {
flock_id: values.flock_id as number, flock_id: values.flock_id as number,
area_id: values.area_id as number, area_id: values.area_id as number,
product_category_id: values.product_category_id as number, category: values.category as string,
fcr_id: values.fcr_id as number, fcr_id: values.fcr_id as number,
location_id: values.location_id as number, location_id: values.location_id as number,
period: values.period as number, period: values.period as number,
@@ -526,18 +533,15 @@ const ProjectFlockForm = ({
/> />
<SelectInput <SelectInput
required required
label='Kategori Produk' label='Kategori'
value={formik.values.product_category as OptionType} value={formik.values.category_option as OptionType}
onChange={(val) => { onChange={categoryChangeHandler}
optionChangeHandler(val, 'product_category'); options={FLOCK_CATEGORY_OPTIONS}
}}
options={optionsProductCategory}
isLoading={isLoadingProductCategories}
isError={ isError={
formik.touched.product_category && formik.touched.category &&
Boolean(formik.errors.product_category) Boolean(formik.errors.category)
} }
errorMessage={formik.errors.product_category as string} errorMessage={formik.errors.category as string}
isClearable isClearable
isDisabled={formType === 'detail'} isDisabled={formType === 'detail'}
/> />
@@ -554,6 +558,7 @@ const ProjectFlockForm = ({
} }
errorMessage={formik.errors.period as string} errorMessage={formik.errors.period as string}
readOnly={formType === 'detail'} readOnly={formType === 'detail'}
disabled={true}
/> />
</div> </div>
</div> </div>
@@ -592,19 +597,24 @@ const ProjectFlockForm = ({
type='checkbox' type='checkbox'
checked={ checked={
optionsKandang optionsKandang
.filter((k) => k.status === 'NON_ACTIVE') .filter((k) => (k.status === 'NON_ACTIVE' || formik.values.kandang_ids.includes(k.id)))
.every((k) => .every((k) =>
formik.values.kandang_ids.includes(k.id) formik.values.kandang_ids.includes(k.id)
) && ) &&
optionsKandang.filter( optionsKandang.filter(
(k) => k.status === 'NON_ACTIVE' (k) => (k.status === 'NON_ACTIVE' || formik.values.kandang_ids.includes(k.id))
).length > 0 ).length > 0
} }
className='checkbox' className='checkbox'
disabled={formType === 'detail' || optionsKandang.filter(
(k) => (k.status === 'NON_ACTIVE')
).length == 0}
onChange={ onChange={
formType === 'detail' formType === 'detail'
? () => {} ? () => {}
: kandangCheckAll : kandangCheckAll
} }
/> />
</label> </label>
@@ -635,7 +645,7 @@ const ProjectFlockForm = ({
} }
disabled={ disabled={
formType === 'detail' || formType === 'detail' ||
kandang.status != 'NON_ACTIVE' (kandang.status != 'NON_ACTIVE')
} }
/> />
</label> </label>
+11
View File
@@ -189,6 +189,17 @@ export const CATEGORY_OPTIONS = [
}, },
]; ];
export const FLOCK_CATEGORY_OPTIONS = [
{
label: 'GROWING',
value: 'GROWING',
},
{
label: 'LAYING',
value: 'LAYING',
},
];
export const PRODUCT_FLAG_OPTIONS = [ export const PRODUCT_FLAG_OPTIONS = [
{ label: 'DOC', value: 'DOC' }, { label: 'DOC', value: 'DOC' },
{ label: 'PAKAN', value: 'PAKAN' }, { label: 'PAKAN', value: 'PAKAN' },
+2 -3
View File
@@ -12,8 +12,7 @@ export type BaseProjectFlock = {
flock_id: number; flock_id: number;
area: Area; area: Area;
area_id: number; area_id: number;
product_category: ProductCategory; category: string;
product_category_id: number;
fcr: Fcr; fcr: Fcr;
fcr_id: number; fcr_id: number;
location: Location; location: Location;
@@ -34,7 +33,7 @@ export type ProjectFlock = BaseMetadata & BaseProjectFlock
export type CreateProjectFlockPayload = { export type CreateProjectFlockPayload = {
flock_id: number; flock_id: number;
area_id: number; area_id: number;
product_category_id: number; category: string;
fcr_id: number; fcr_id: number;
location_id: number; location_id: number;
period: number; period: number;