diff --git a/.husky/pre-commit b/.husky/pre-commit index f799d12f..de6b606b 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,4 @@ npm run format npm run lint -npm run typecheck \ No newline at end of file +npm run typecheck +git add . diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index 83b8015f..c7a0d0f3 100644 --- a/src/components/pages/expense/ExpensesTable.tsx +++ b/src/components/pages/expense/ExpensesTable.tsx @@ -54,6 +54,13 @@ type ExpenseTableFilters = { locationName: string; vendorId: string; vendorName: string; + category: string; + approvalStatus: string; + realizationStatus: string; + projectFlockId: string; + projectFlockName: string; + projectFlockKandangId: string; + projectFlockKandangName: string; userId: string; }; @@ -250,6 +257,13 @@ const ExpensesTable = () => { locationName: '', vendorId: '', vendorName: '', + category: '', + approvalStatus: '', + realizationStatus: '', + projectFlockId: '', + projectFlockName: '', + projectFlockKandangId: '', + projectFlockKandangName: '', userId: '', }, paramMap: { @@ -262,6 +276,13 @@ const ExpensesTable = () => { locationName: 'location_name', vendorId: 'vendor_id', vendorName: 'vendor_name', + category: 'category', + approvalStatus: 'approval_status', + realizationStatus: 'realization_status', + projectFlockId: 'project_flock_id', + projectFlockName: 'project_flock_name', + projectFlockKandangId: 'project_flock_kandang_id', + projectFlockKandangName: 'project_flock_kandang_name', userId: 'user_id', }, @@ -749,6 +770,11 @@ const ExpensesTable = () => { realization_date?: string | null; location?: { value: number; label: string } | null; vendor?: { value: number; label: string } | null; + category?: OptionType | null; + approval_status?: OptionType | null; + realization_status?: OptionType | null; + project_flock?: OptionType | null; + project_flock_kandang?: OptionType | null; }) => { updateFilter('transactionDate', values.transaction_date || ''); updateFilter('realizationDate', values.realization_date || ''); @@ -768,6 +794,24 @@ const ExpensesTable = () => { 'vendorName', values.vendor?.label ? String(values.vendor?.label) : '' ); + updateFilter('category', values.category?.value || ''); + updateFilter('approvalStatus', values.approval_status?.value || ''); + updateFilter('realizationStatus', values.realization_status?.value || ''); + updateFilter( + 'projectFlockId', + values.project_flock?.value ? String(values.project_flock.value) : '' + ); + updateFilter('projectFlockName', values.project_flock?.label || ''); + updateFilter( + 'projectFlockKandangId', + values.project_flock_kandang?.value + ? String(values.project_flock_kandang.value) + : '' + ); + updateFilter( + 'projectFlockKandangName', + values.project_flock_kandang?.label || '' + ); }; const handleFilterReset = () => { @@ -947,6 +991,8 @@ const ExpensesTable = () => { 'userId', 'locationName', 'vendorName', + 'projectFlockName', + 'projectFlockKandangName', ]} onClick={handleFilterModalOpen} className='px-3 py-2.5' @@ -1282,6 +1328,41 @@ const ExpensesTable = () => { : null, realization_date: tableFilterState.realizationDate, transaction_date: tableFilterState.transactionDate, + category: tableFilterState.category + ? { + value: tableFilterState.category, + label: tableFilterState.category, + } + : null, + approval_status: tableFilterState.approvalStatus + ? approvalStatusOptions.find( + (item) => item.value === tableFilterState.approvalStatus + ) || null + : null, + realization_status: tableFilterState.realizationStatus + ? [ + { value: 'NOT_REALIZED', label: 'Belum Realisasi' }, + { value: 'REALIZED', label: 'Sudah Realisasi' }, + { value: 'REJECTED', label: 'Ditolak' }, + ].find( + (item) => item.value === tableFilterState.realizationStatus + ) || null + : null, + project_flock: + tableFilterState.projectFlockId && tableFilterState.projectFlockName + ? { + value: Number(tableFilterState.projectFlockId), + label: tableFilterState.projectFlockName, + } + : null, + project_flock_kandang: + tableFilterState.projectFlockKandangId && + tableFilterState.projectFlockKandangName + ? { + value: Number(tableFilterState.projectFlockKandangId), + label: tableFilterState.projectFlockKandangName, + } + : null, }} /> diff --git a/src/components/pages/expense/filter/ExpensesFilter.ts b/src/components/pages/expense/filter/ExpensesFilter.ts index 703863ea..7c833f2a 100644 --- a/src/components/pages/expense/filter/ExpensesFilter.ts +++ b/src/components/pages/expense/filter/ExpensesFilter.ts @@ -5,6 +5,11 @@ export type ExpensesFilterType = { realization_date: string | null; location: { value: number; label: string } | null; vendor: { value: number; label: string } | null; + category: { value: string; label: string } | null; + approval_status: { value: string; label: string } | null; + realization_status: { value: string; label: string } | null; + project_flock: { value: number; label: string } | null; + project_flock_kandang: { value: number; label: string } | null; }; export const ExpensesFilterSchema = yup.object({ @@ -33,6 +38,36 @@ export const ExpensesFilterSchema = yup.object({ label: yup.string().required(), }) .nullable(), + category: yup + .object({ + value: yup.string().required(), + label: yup.string().required(), + }) + .nullable(), + approval_status: yup + .object({ + value: yup.string().required(), + label: yup.string().required(), + }) + .nullable(), + realization_status: yup + .object({ + value: yup.string().required(), + label: yup.string().required(), + }) + .nullable(), + project_flock: yup + .object({ + value: yup.number().required(), + label: yup.string().required(), + }) + .nullable(), + project_flock_kandang: yup + .object({ + value: yup.number().required(), + label: yup.string().required(), + }) + .nullable(), }); export type ExpensesFilterValues = yup.InferType; diff --git a/src/components/pages/expense/filter/ExpensesFilterModal.tsx b/src/components/pages/expense/filter/ExpensesFilterModal.tsx index 67e4b0ac..67a6f431 100644 --- a/src/components/pages/expense/filter/ExpensesFilterModal.tsx +++ b/src/components/pages/expense/filter/ExpensesFilterModal.tsx @@ -1,6 +1,6 @@ 'use client'; -import { RefObject, useCallback } from 'react'; +import { RefObject, useCallback, useEffect, useMemo, useState } from 'react'; import { useFormik } from 'formik'; import { Icon } from '@iconify/react'; @@ -11,8 +11,11 @@ import SelectInput from '@/components/input/SelectInput'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import { LocationApi, SupplierApi } from '@/services/api/master-data'; +import { ProjectFlockApi } from '@/services/api/production'; import { Location } from '@/types/api/master-data/location'; import { Supplier } from '@/types/api/master-data/supplier'; +import { ProjectFlock } from '@/types/api/production/project-flock'; +import { isResponseSuccess } from '@/lib/api-helper'; import { ExpensesFilterSchema, ExpensesFilterValues, @@ -31,10 +34,33 @@ const ExpensesFilterModal = ({ onSubmit, onReset, }: ExpensesFilterModalProps) => { + const [selectedLocationId, setSelectedLocationId] = useState( + initialValues?.location?.value ? String(initialValues.location.value) : '' + ); const closeModalHandler = () => { ref.current?.close(); }; + const categoryOptions = [ + { value: 'BOP', label: 'BOP' }, + { value: 'NON-BOP', label: 'NON-BOP' }, + ]; + + const approvalStatusOptions = [ + { value: 'HEAD_AREA', label: 'Approval Head Area' }, + { value: 'UNIT_VICE_PRESIDENT', label: 'Approval Unit Vice President' }, + { value: 'FINANCE', label: 'Approval Finance' }, + { value: 'REALISASI', label: 'Realisasi' }, + { value: 'SELESAI', label: 'Selesai' }, + { value: 'DITOLAK', label: 'Ditolak' }, + ]; + + const realizationStatusOptions = [ + { value: 'NOT_REALIZED', label: 'Belum Realisasi' }, + { value: 'REALIZED', label: 'Sudah Realisasi' }, + { value: 'REJECTED', label: 'Ditolak' }, + ]; + const { setInputValue: setLocationInputValue, options: locationOptions, @@ -49,12 +75,34 @@ const ExpensesFilterModal = ({ loadMore: loadMoreVendors, } = useSelect(SupplierApi.basePath, 'id', 'name'); + const { + setInputValue: setProjectFlockInputValue, + rawData: projectFlocksRawData, + options: projectFlockOptions, + isLoadingOptions: isLoadingProjectFlockOptions, + loadMore: loadMoreProjectFlocks, + } = useSelect( + ProjectFlockApi.basePath, + 'id', + 'flock_name', + 'search', + { + location_id: selectedLocationId || '', + } + ); + const formik = useFormik({ + enableReinitialize: true, initialValues: initialValues || { transaction_date: null, realization_date: null, location: null, vendor: null, + category: null, + approval_status: null, + realization_status: null, + project_flock: null, + project_flock_kandang: null, }, validationSchema: ExpensesFilterSchema, onSubmit: async (values) => { @@ -63,6 +111,12 @@ const ExpensesFilterModal = ({ }, }); + useEffect(() => { + setSelectedLocationId( + initialValues?.location?.value ? String(initialValues.location.value) : '' + ); + }, [initialValues?.location]); + const { resetForm } = formik; const formikResetHandler = useCallback(() => { @@ -72,20 +126,51 @@ const ExpensesFilterModal = ({ realization_date: null, location: null, vendor: null, + category: null, + approval_status: null, + realization_status: null, + project_flock: null, + project_flock_kandang: null, }, }); + setSelectedLocationId(''); onReset?.(); closeModalHandler(); }, [resetForm, onReset, closeModalHandler]); const locationChangeHandler = (val: OptionType | OptionType[] | null) => { - formik.setFieldValue('location', val as OptionType | null); + const value = val as OptionType | null; + formik.setFieldValue('location', value); + formik.setFieldValue('project_flock', null); + formik.setFieldValue('project_flock_kandang', null); + setSelectedLocationId(value?.value ? String(value.value) : ''); }; const vendorChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldValue('vendor', val as OptionType | null); }; + const projectFlockKandangOptions = useMemo(() => { + if ( + !formik.values.project_flock || + !projectFlocksRawData || + !isResponseSuccess(projectFlocksRawData) + ) { + return []; + } + + const selectedProjectFlock = projectFlocksRawData.data.find( + (item) => item.id === formik.values.project_flock?.value + ); + + return ( + selectedProjectFlock?.kandangs?.map((item) => ({ + value: item.project_flock_kandang_id, + label: item.name, + })) || [] + ); + }, [formik.values.project_flock, projectFlocksRawData]); + return ( + + + formik.setFieldValue('category', val as OptionType | null) + } + isClearable + className={{ wrapper: 'w-full' }} + /> + + + formik.setFieldValue('approval_status', val as OptionType | null) + } + isClearable + className={{ wrapper: 'w-full' }} + /> + + + formik.setFieldValue( + 'realization_status', + val as OptionType | null + ) + } + isClearable + className={{ wrapper: 'w-full' }} + /> + + { + formik.setFieldValue('project_flock', val as OptionType | null); + formik.setFieldValue('project_flock_kandang', null); + }} + onInputChange={setProjectFlockInputValue} + isLoading={isLoadingProjectFlockOptions} + onMenuScrollToBottom={loadMoreProjectFlocks} + isClearable + isSearchable={true} + className={{ wrapper: 'w-full' }} + /> + + + formik.setFieldValue( + 'project_flock_kandang', + val as OptionType | null + ) + } + isClearable + isDisabled={!formik.values.project_flock} + className={{ wrapper: 'w-full' }} + /> {/* Modal Footer */} diff --git a/src/components/pages/marketing/MarketingFilter.tsx b/src/components/pages/marketing/MarketingFilter.tsx index 823d2dd2..86298598 100644 --- a/src/components/pages/marketing/MarketingFilter.tsx +++ b/src/components/pages/marketing/MarketingFilter.tsx @@ -20,6 +20,8 @@ import { MarketingApi } from '@/services/api/marketing/marketing'; import { CustomerApi } from '@/services/api/master-data'; import { isResponseSuccess } from '@/lib/api-helper'; import { BaseMarketing, BaseSalesOrder } from '@/types/api/marketing/marketing'; +import { ProjectFlockApi } from '@/services/api/production'; +import { ProjectFlock } from '@/types/api/production/project-flock'; interface MarketingFilterModal { ref: RefObject; @@ -78,6 +80,19 @@ const MarketingFilterModal = ({ has_marketing: 'true', }); + const { + options: projectFlockOptions, + rawData: projectFlocksRawData, + isLoadingOptions: isLoadingProjectFlockOptions, + setInputValue: setProjectFlockInputValue, + loadMore: loadMoreProjectFlocks, + } = useSelect( + ProjectFlockApi.basePath, + 'id', + 'flock_name', + 'search' + ); + const statusOptions = [ ...MARKETING_APPROVAL_LINE.map((item) => ({ value: item.step_name.split(' ').join('_').toUpperCase(), @@ -91,6 +106,8 @@ const MarketingFilterModal = ({ product_ids: [], status: null, customer: null, + project_flock: null, + project_flock_kandang: null, }, validationSchema: MarketingFilterSchema, @@ -99,6 +116,9 @@ const MarketingFilterModal = ({ product_ids: values.product_ids.map((item) => Number(item.value)), status: values.status?.value.toString() || '', customer_id: Number(values.customer?.value), + project_flock_id: Number(values.project_flock?.value) || undefined, + project_flock_kandang_id: + Number(values.project_flock_kandang?.value) || undefined, }; onSubmit?.(formattedValues); @@ -126,6 +146,27 @@ const MarketingFilterModal = ({ formik.setFieldValue('status', val as OptionType); }; + const projectFlockKandangOptions = useMemo(() => { + if ( + !formik.values.project_flock || + !projectFlocksRawData || + !isResponseSuccess(projectFlocksRawData) + ) { + return []; + } + + const selectedProjectFlock = projectFlocksRawData.data.find( + (item) => item.id === formik.values.project_flock?.value + ); + + return ( + selectedProjectFlock?.kandangs?.map((item) => ({ + value: item.project_flock_kandang_id, + label: item.name, + })) || [] + ); + }, [formik.values.project_flock, projectFlocksRawData]); + return ( + { + formik.setFieldValue( + 'project_flock', + !Array.isArray(val) ? (val as OptionType | null) : null + ); + formik.setFieldValue('project_flock_kandang', null); + }} + onInputChange={setProjectFlockInputValue} + onMenuScrollToBottom={loadMoreProjectFlocks} + /> + + formik.setFieldValue( + 'project_flock_kandang', + !Array.isArray(val) ? (val as OptionType | null) : null + ) + } + isDisabled={!formik.values.project_flock} + /> {/* Modal Footer */} diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index 63e700da..cda92d31 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -224,6 +224,8 @@ const MarketingTable = () => { product_ids: '', status: '', customer_id: '', + project_flock_id: '', + project_flock_kandang_id: '', }, paramMap: { page: 'page', @@ -231,6 +233,8 @@ const MarketingTable = () => { product_ids: 'product_ids', status: 'status', customer_id: 'customer_id', + project_flock_id: 'project_flock_id', + project_flock_kandang_id: 'project_flock_kandang_id', }, persist: true, @@ -260,6 +264,18 @@ const MarketingTable = () => { values.customer_id ? values.customer_id.toString() : '', true ); + updateFilter( + 'project_flock_id', + values.project_flock_id ? values.project_flock_id.toString() : '', + true + ); + updateFilter( + 'project_flock_kandang_id', + values.project_flock_kandang_id + ? values.project_flock_kandang_id.toString() + : '', + true + ); }; const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = @@ -269,6 +285,8 @@ const MarketingTable = () => { updateFilter('product_ids', '', true); updateFilter('status', '', true); updateFilter('customer_id', '', true); + updateFilter('project_flock_id', '', true); + updateFilter('project_flock_kandang_id', '', true); }; const approveClickHandler = () => { diff --git a/src/components/pages/marketing/filter/MarketingFilter.ts b/src/components/pages/marketing/filter/MarketingFilter.ts index 4ff9a792..a306d89d 100644 --- a/src/components/pages/marketing/filter/MarketingFilter.ts +++ b/src/components/pages/marketing/filter/MarketingFilter.ts @@ -5,10 +5,14 @@ export const MarketingFilterSchema = object({ product_ids: array().of(mixed>().required()).required(), status: mixed>().nullable(), customer: mixed>().nullable(), + project_flock: mixed>().nullable(), + project_flock_kandang: mixed>().nullable(), }); export type MarketingFilterFormValues = { product_ids: OptionType[]; status: OptionType | null; customer: OptionType | null; + project_flock: OptionType | null; + project_flock_kandang: OptionType | null; }; diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index b31d1bb9..2f3ce848 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -319,15 +319,23 @@ const RecordingTable = () => { search: '', areaFilter: '', locationFilter: '', + projectFlockFilter: '', kandangFilter: '', projectFlockKandangFilter: '', + approvalStatusFilter: '', + projectFlockCategoryFilter: '', }, paramMap: { page: 'page', pageSize: 'limit', search: 'search', + areaFilter: 'area_id', + locationFilter: 'location_id', + projectFlockFilter: 'project_flock_id', kandangFilter: 'kandang_id', projectFlockKandangFilter: 'project_flock_kandang_id', + approvalStatusFilter: 'approval_status', + projectFlockCategoryFilter: 'project_flock_category', }, }); @@ -356,26 +364,38 @@ const RecordingTable = () => { initialValues: { area_id: null, location_id: null, + project_flock_id: null, kandang_id: null, project_flock_kandang_id: null, + approval_status: null, + project_flock_category: null, }, validationSchema: RecordingFilterSchema, onSubmit: (values, { setSubmitting }) => { updateFilter('areaFilter', values.area_id || ''); updateFilter('locationFilter', values.location_id || ''); + updateFilter('projectFlockFilter', values.project_flock_id || ''); updateFilter('kandangFilter', values.kandang_id || ''); updateFilter( 'projectFlockKandangFilter', values.project_flock_kandang_id || '' ); + updateFilter('approvalStatusFilter', values.approval_status || ''); + updateFilter( + 'projectFlockCategoryFilter', + values.project_flock_category || '' + ); filterModal.closeModal(); setSubmitting(false); }, onReset: () => { updateFilter('areaFilter', ''); updateFilter('locationFilter', ''); + updateFilter('projectFlockFilter', ''); updateFilter('kandangFilter', ''); updateFilter('projectFlockKandangFilter', ''); + updateFilter('approvalStatusFilter', ''); + updateFilter('projectFlockCategoryFilter', ''); }, }); @@ -537,6 +557,7 @@ const RecordingTable = () => { formik.setFieldValue('area_id', areaId); formik.setFieldValue('location_id', null); + formik.setFieldValue('project_flock_id', null); formik.setFieldValue('kandang_id', null); formik.setFieldValue('project_flock_kandang_id', null); @@ -556,6 +577,7 @@ const RecordingTable = () => { const locationId = location?.value ? String(location.value) : null; formik.setFieldValue('location_id', locationId); + formik.setFieldValue('project_flock_id', null); formik.setFieldValue('kandang_id', null); formik.setFieldValue('project_flock_kandang_id', null); @@ -570,7 +592,11 @@ const RecordingTable = () => { const handleFilterProjectFlockChange = useCallback( (val: OptionType | OptionType[] | null) => { const projectFlock = val as OptionType | null; + const projectFlockId = projectFlock?.value + ? String(projectFlock.value) + : null; + formik.setFieldValue('project_flock_id', projectFlockId); formik.setFieldValue('kandang_id', null); formik.setFieldValue('project_flock_kandang_id', null); @@ -625,6 +651,36 @@ const RecordingTable = () => { ); }, [formik.values.kandang_id, kandangOptions]); + const recordingApprovalStatusOptions: OptionType[] = [ + { value: 'CREATED', label: 'Pengajuan' }, + { value: 'UPDATED', label: 'Diperbarui' }, + { value: 'APPROVED', label: 'Disetujui' }, + { value: 'REJECTED', label: 'Ditolak' }, + ]; + + const projectFlockCategoryOptions: OptionType[] = [ + { value: 'GROWING', label: 'Growing' }, + { value: 'LAYING', label: 'Laying' }, + ]; + + const approvalStatusValue = useMemo(() => { + if (!formik.values.approval_status) return null; + return ( + recordingApprovalStatusOptions.find( + (opt) => opt.value === formik.values.approval_status + ) || null + ); + }, [formik.values.approval_status]); + + const projectFlockCategoryValue = useMemo(() => { + if (!formik.values.project_flock_category) return null; + return ( + projectFlockCategoryOptions.find( + (opt) => opt.value === formik.values.project_flock_category + ) || null + ); + }, [formik.values.project_flock_category]); + // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { filterModal.openModal(); @@ -1607,6 +1663,36 @@ const RecordingTable = () => { isDisabled={!filterProjectFlock} className={{ wrapper: 'w-full' }} /> + + { + formik.setFieldValue( + 'project_flock_category', + !Array.isArray(val) && val ? String(val.value) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> + + { + formik.setFieldValue( + 'approval_status', + !Array.isArray(val) && val ? String(val.value) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> {/* Modal Footer */} @@ -1631,11 +1717,7 @@ const RecordingTable = () => { diff --git a/src/components/pages/production/recording/filter/RecordingFilter.ts b/src/components/pages/production/recording/filter/RecordingFilter.ts index 955ae744..01caced0 100644 --- a/src/components/pages/production/recording/filter/RecordingFilter.ts +++ b/src/components/pages/production/recording/filter/RecordingFilter.ts @@ -3,13 +3,19 @@ import { string, object } from 'yup'; export const RecordingFilterSchema = object().shape({ area_id: string().nullable(), location_id: string().nullable(), + project_flock_id: string().nullable(), kandang_id: string().nullable(), project_flock_kandang_id: string().nullable(), + approval_status: string().nullable(), + project_flock_category: string().nullable(), }); export type RecordingFilterType = { area_id: string | null; location_id: string | null; + project_flock_id: string | null; kandang_id: string | null; project_flock_kandang_id: string | null; + approval_status: string | null; + project_flock_category: string | null; }; diff --git a/src/components/pages/purchase/PurchaseFilterModal.tsx b/src/components/pages/purchase/PurchaseFilterModal.tsx index a9cd00cd..bcb80fe6 100644 --- a/src/components/pages/purchase/PurchaseFilterModal.tsx +++ b/src/components/pages/purchase/PurchaseFilterModal.tsx @@ -1,6 +1,6 @@ 'use client'; -import { RefObject, useState, useEffect } from 'react'; +import { RefObject, useState, useEffect, useMemo } from 'react'; import { useFormik } from 'formik'; import toast from 'react-hot-toast'; @@ -9,12 +9,20 @@ import Modal from '@/components/Modal'; import Button from '@/components/Button'; import DateInput from '@/components/input/DateInput'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; +import SelectInput from '@/components/input/SelectInput'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import { PurchaseFilter } from '@/types/api/purchase/purchase'; +import { AreaApi, LocationApi, SupplierApi } from '@/services/api/master-data'; import { ProductCategory } from '@/types/api/master-data/product-category'; import { ProductCategoryApi } from '@/services/api/master-data'; +import { Area } from '@/types/api/master-data/area'; +import { Location } from '@/types/api/master-data/location'; +import { Supplier } from '@/types/api/master-data/supplier'; import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line'; +import { ProjectFlockApi } from '@/services/api/production'; +import { ProjectFlock } from '@/types/api/production/project-flock'; +import { isResponseSuccess } from '@/lib/api-helper'; interface PurchaseFilterModalProps { ref: RefObject; @@ -73,32 +81,112 @@ const PurchaseFilterModal = ({ 'search' ); + const [selectedAreaId, setSelectedAreaId] = useState(''); + const [selectedLocationId, setSelectedLocationId] = useState(''); + + const { + setInputValue: setSupplierInputValue, + options: supplierOptions, + isLoadingOptions: isLoadingSupplierOptions, + loadMore: loadMoreSuppliers, + } = useSelect(SupplierApi.basePath, 'id', 'name', 'search'); + + const { + setInputValue: setAreaInputValue, + options: areaOptions, + isLoadingOptions: isLoadingAreaOptions, + loadMore: loadMoreAreas, + } = useSelect(AreaApi.basePath, 'id', 'name', 'search'); + + const { + setInputValue: setLocationInputValue, + options: locationOptions, + isLoadingOptions: isLoadingLocationOptions, + loadMore: loadMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search', { + area_id: selectedAreaId || '', + }); + + const { + setInputValue: setProjectFlockInputValue, + options: projectFlockOptions, + rawData: projectFlocksRawData, + isLoadingOptions: isLoadingProjectFlockOptions, + loadMore: loadMoreProjectFlocks, + } = useSelect( + ProjectFlockApi.basePath, + 'id', + 'flock_name', + 'search', + { + location_id: selectedLocationId || '', + } + ); + const formik = useFormik<{ poDate: string; category: { label: string; value: number }[]; status: { label: string; value: string }[]; + supplier: OptionType | null; + area: OptionType | null; + location: OptionType | null; + project_flock: OptionType | null; + project_flock_kandang: OptionType | null; }>({ initialValues: { poDate: '', category: [], status: [], + supplier: null, + area: null, + location: null, + project_flock: null, + project_flock_kandang: null, }, onSubmit: async (values) => { const formattedValues = { ...values, category: values.category.map((item) => String(item.value)), status: values.status.map((item) => String(item.value)), + supplier_id: values.supplier?.value, + area_id: values.area?.value, + location_id: values.location?.value, + project_flock_id: values.project_flock?.value, + project_flock_kandang_id: values.project_flock_kandang?.value, }; onSubmit?.(formattedValues); closeModalHandler(); }, onReset: () => { + setSelectedAreaId(''); + setSelectedLocationId(''); onReset?.(); closeModalHandler(); }, }); + const projectFlockKandangOptions = useMemo(() => { + if ( + !formik.values.project_flock || + !projectFlocksRawData || + !isResponseSuccess(projectFlocksRawData) + ) { + return []; + } + + const selectedProjectFlock = projectFlocksRawData.data.find( + (item) => item.id === formik.values.project_flock?.value + ); + + return ( + selectedProjectFlock?.kandangs?.map((item) => ({ + value: item.project_flock_kandang_id, + label: item.name, + })) || [] + ); + }, [formik.values.project_flock, projectFlocksRawData]); + const productCategoryChangeHandler = ( val: OptionType | OptionType[] | null ) => { @@ -172,6 +260,108 @@ const PurchaseFilterModal = ({ value: item.step_name, }))} /> + + + formik.setFieldValue( + 'supplier', + !Array.isArray(val) + ? (val as OptionType | null) + : null + ) + } + options={supplierOptions} + isLoading={isLoadingSupplierOptions} + onInputChange={setSupplierInputValue} + onMenuScrollToBottom={loadMoreSuppliers} + isClearable + /> + + { + const nextValue = !Array.isArray(val) + ? (val as OptionType | null) + : null; + formik.setFieldValue('area', nextValue); + formik.setFieldValue('location', null); + formik.setFieldValue('project_flock', null); + formik.setFieldValue('project_flock_kandang', null); + setSelectedAreaId( + nextValue?.value ? String(nextValue.value) : '' + ); + setSelectedLocationId(''); + }} + options={areaOptions} + isLoading={isLoadingAreaOptions} + onInputChange={setAreaInputValue} + onMenuScrollToBottom={loadMoreAreas} + isClearable + /> + + { + const nextValue = !Array.isArray(val) + ? (val as OptionType | null) + : null; + formik.setFieldValue('location', nextValue); + formik.setFieldValue('project_flock', null); + formik.setFieldValue('project_flock_kandang', null); + setSelectedLocationId( + nextValue?.value ? String(nextValue.value) : '' + ); + }} + options={locationOptions} + isLoading={isLoadingLocationOptions} + onInputChange={setLocationInputValue} + onMenuScrollToBottom={loadMoreLocations} + isClearable + isDisabled={!formik.values.area} + /> + + { + const nextValue = !Array.isArray(val) + ? (val as OptionType | null) + : null; + formik.setFieldValue('project_flock', nextValue); + formik.setFieldValue('project_flock_kandang', null); + }} + options={projectFlockOptions} + isLoading={isLoadingProjectFlockOptions} + onInputChange={setProjectFlockInputValue} + onMenuScrollToBottom={loadMoreProjectFlocks} + isClearable + isDisabled={!formik.values.location} + /> + + + formik.setFieldValue( + 'project_flock_kandang', + !Array.isArray(val) + ? (val as OptionType | null) + : null + ) + } + options={projectFlockKandangOptions} + isClearable + isDisabled={!formik.values.project_flock} + /> diff --git a/src/components/pages/purchase/PurchaseTable.tsx b/src/components/pages/purchase/PurchaseTable.tsx index 67555522..c15bbfbb 100644 --- a/src/components/pages/purchase/PurchaseTable.tsx +++ b/src/components/pages/purchase/PurchaseTable.tsx @@ -213,6 +213,11 @@ const PurchaseTable = () => { po_date: '', approval_status: '', product_category_id: '', + supplier_id: '', + area_id: '', + location_id: '', + project_flock_id: '', + project_flock_kandang_id: '', }, paramMap: { page: 'page', @@ -220,6 +225,11 @@ const PurchaseTable = () => { po_date: 'po_date', approval_status: 'approval_status', product_category_id: 'product_category_id', + supplier_id: 'supplier_id', + area_id: 'area_id', + location_id: 'location_id', + project_flock_id: 'project_flock_id', + project_flock_kandang_id: 'project_flock_kandang_id', }, }); @@ -467,12 +477,36 @@ const PurchaseTable = () => { updateFilter('po_date', values.poDate); updateFilter('product_category_id', values.category.join(',')); updateFilter('approval_status', values.status.join(',')); + updateFilter( + 'supplier_id', + values.supplier_id ? String(values.supplier_id) : '' + ); + updateFilter('area_id', values.area_id ? String(values.area_id) : ''); + updateFilter( + 'location_id', + values.location_id ? String(values.location_id) : '' + ); + updateFilter( + 'project_flock_id', + values.project_flock_id ? String(values.project_flock_id) : '' + ); + updateFilter( + 'project_flock_kandang_id', + values.project_flock_kandang_id + ? String(values.project_flock_kandang_id) + : '' + ); }; const filterResetHandler = () => { updateFilter('po_date', ''); updateFilter('product_category_id', ''); updateFilter('approval_status', ''); + updateFilter('supplier_id', ''); + updateFilter('area_id', ''); + updateFilter('location_id', ''); + updateFilter('project_flock_id', ''); + updateFilter('project_flock_kandang_id', ''); }; const resetExportProgressForm = useCallback(() => { diff --git a/src/types/api/marketing/marketing.d.ts b/src/types/api/marketing/marketing.d.ts index f89954d8..e9967a2b 100644 --- a/src/types/api/marketing/marketing.d.ts +++ b/src/types/api/marketing/marketing.d.ts @@ -97,6 +97,8 @@ export type MarketingFilter = { product_ids: number[]; status: string; customer_id: number; + project_flock_id?: number; + project_flock_kandang_id?: number; }; /** diff --git a/src/types/api/purchase/purchase.d.ts b/src/types/api/purchase/purchase.d.ts index b0abe694..d2dec108 100644 --- a/src/types/api/purchase/purchase.d.ts +++ b/src/types/api/purchase/purchase.d.ts @@ -149,4 +149,9 @@ export type PurchaseFilter = { poDate: string; category: string[]; status: string[]; + supplier_id?: number; + area_id?: number; + location_id?: number; + project_flock_id?: number; + project_flock_kandang_id?: number; };