Merge branch 'development' into 'production'

refactor(FE-add-param): Update MarketingFilter to refine API calls and

See merge request mbugroup/lti-web-client!400
This commit is contained in:
Adnan Zahir
2026-04-14 13:20:27 +07:00
5 changed files with 45 additions and 89 deletions
@@ -5,11 +5,14 @@ import { Icon } from '@iconify/react';
import Modal, { useModal } from '@/components/Modal'; import Modal, { useModal } from '@/components/Modal';
import DateInput from '@/components/input/DateInput'; import DateInput from '@/components/input/DateInput';
import { OptionType, useSelect } from '@/components/input/SelectInput'; import { OptionType, useSelect } from '@/components/input/SelectInput';
import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { DashboardApi } from '@/services/api/dashboard'; import { DashboardApi } from '@/services/api/dashboard';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { ProjectFlockApi } from '@/services/api/production'; import {
ProjectFlockApi,
ProjectFlockKandangApi,
} from '@/services/api/production';
import { LocationApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data';
import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF'; import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF';
import { import {
@@ -22,10 +25,7 @@ import DashboardExportCharts, {
DashboardExportChartsRef, DashboardExportChartsRef,
} from '@/components/pages/dashboard/export/DashboardExportCharts'; } from '@/components/pages/dashboard/export/DashboardExportCharts';
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput'; import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
import { import { DashboardMeta } from '@/types/api/dashboard/dashboard';
DashboardFilter,
DashboardMeta,
} from '@/types/api/dashboard/dashboard';
import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats'; import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import AlertErrorList from '@/components/helper/form/FormErrors'; import AlertErrorList from '@/components/helper/form/FormErrors';
@@ -43,6 +43,7 @@ import DashboardExportStats, {
DashboardExportStatsRef, DashboardExportStatsRef,
} from '@/components/pages/dashboard/export/DashboardExportStats'; } from '@/components/pages/dashboard/export/DashboardExportStats';
import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlock } from '@/types/api/production/project-flock';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
// Helper function to normalize values to array // Helper function to normalize values to array
const normalizeToArray = ( const normalizeToArray = (
@@ -72,7 +73,6 @@ const DashboardProduction = () => {
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>( const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>(
normalizeToArray(filterValues.location) normalizeToArray(filterValues.location)
); );
const [kandangInputValue, setRawKandangInputValue] = useState('');
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const allChartsRef = useRef<DashboardExportChartsRef>(null); const allChartsRef = useRef<DashboardExportChartsRef>(null);
const allStatsRef = useRef<DashboardExportStatsRef>(null); const allStatsRef = useRef<DashboardExportStatsRef>(null);
@@ -116,15 +116,13 @@ const DashboardProduction = () => {
options: flockOptions, options: flockOptions,
isLoadingOptions: isLoadingFlockOptions, isLoadingOptions: isLoadingFlockOptions,
loadMore: loadMoreFlock, loadMore: loadMoreFlock,
rawData: projectFlocksRawData,
} = useSelect<ProjectFlock>( } = useSelect<ProjectFlock>(
ProjectFlockApi.basePath, ProjectFlockApi.basePath,
'id', 'id',
'flock_name', 'flock_name',
'', 'search',
{ {
location_id: location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
selectedLocationIds.length > 0 ? selectedLocationIds.toString() : '',
} }
); );
@@ -165,66 +163,34 @@ const DashboardProduction = () => {
const { resetForm } = formik; const { resetForm } = formik;
const selectedFlockIds = useMemo( const selectedLocationValues = normalizeToArray(formik.values.location);
() => normalizeToArray(formik.values.flock), const selectedFlockValues = normalizeToArray(formik.values.flock);
[formik.values.flock]
);
const derivedKandangOptions = useMemo(() => {
if (!isResponseSuccess(projectFlocksRawData)) return [];
const availableProjectFlocks = projectFlocksRawData.data.filter(
(projectFlock) =>
selectedFlockIds.length === 0 ||
selectedFlockIds.includes(projectFlock.id)
);
const kandangMap = new Map<number, OptionType<number>>();
availableProjectFlocks.forEach((projectFlock) => {
projectFlock.kandangs?.forEach((kandang) => {
if (!kandangMap.has(kandang.id)) {
kandangMap.set(kandang.id, {
value: kandang.id,
label: kandang.name,
});
}
});
});
const normalizedSearch = kandangInputValue.trim().toLowerCase();
const allOptions = Array.from(kandangMap.values());
if (!normalizedSearch) return allOptions;
return allOptions.filter((option) =>
option.label.toLowerCase().includes(normalizedSearch)
);
}, [projectFlocksRawData, selectedFlockIds, kandangInputValue]);
const kandangSelect = useMemo(
() => ({
setInputValue: setRawKandangInputValue,
options: derivedKandangOptions,
isLoadingOptions: isLoadingFlockOptions,
loadMore: loadMoreFlock,
}),
[derivedKandangOptions, isLoadingFlockOptions, loadMoreFlock]
);
const { const {
setInputValue: setInputValueKandang, setInputValue: setInputValueKandang,
options: kandangOptions, options: kandangOptions,
isLoadingOptions: isLoadingKandangOptions, isLoadingOptions: isLoadingKandangOptions,
loadMore: loadMoreKandang, loadMore: loadMoreKandang,
} = kandangSelect; } = useSelect<ProjectFlockKandang>(
ProjectFlockKandangApi.basePath,
'kandang_id',
'kandang.name',
'search',
{
location_id:
selectedLocationValues.length > 0
? selectedLocationValues.toString()
: '',
project_flock_id:
selectedFlockValues.length > 0 ? selectedFlockValues.toString() : '',
}
);
const handleResetFilter = useCallback(() => { const handleResetFilter = useCallback(() => {
resetForm(); resetForm();
resetFilterValues(); // Clear stored filter values resetFilterValues(); // Clear stored filter values
setAnalysisMode('OVERVIEW'); setAnalysisMode('OVERVIEW');
setSelectedLocationIds([]); setSelectedLocationIds([]);
setRawKandangInputValue('');
filterModal.closeModal(); filterModal.closeModal();
}, [filterModal, resetForm, resetFilterValues]); }, [filterModal, resetForm, resetFilterValues]);
@@ -520,7 +486,6 @@ const DashboardProduction = () => {
formik.setFieldValue('kandang', []); formik.setFieldValue('kandang', []);
formik.setFieldValue('comparisonType', ''); formik.setFieldValue('comparisonType', '');
setSelectedLocationIds([]); setSelectedLocationIds([]);
setRawKandangInputValue('');
}} }}
color='primary' color='primary'
className={{ className={{
@@ -592,7 +557,6 @@ const DashboardProduction = () => {
// Reset dependent fields when location changes // Reset dependent fields when location changes
formik.setFieldValue('flock', []); formik.setFieldValue('flock', []);
formik.setFieldValue('kandang', []); formik.setFieldValue('kandang', []);
setRawKandangInputValue('');
}} }}
errorMessage={formik.errors.location as string} errorMessage={formik.errors.location as string}
options={locationOptions} options={locationOptions}
@@ -625,7 +589,6 @@ const DashboardProduction = () => {
// Reset dependent fields when location changes // Reset dependent fields when location changes
formik.setFieldValue('flock', []); formik.setFieldValue('flock', []);
formik.setFieldValue('kandang', []); formik.setFieldValue('kandang', []);
setRawKandangInputValue('');
}} }}
errorMessage={formik.errors.location as string} errorMessage={formik.errors.location as string}
options={locationOptions} options={locationOptions}
@@ -662,11 +625,9 @@ const DashboardProduction = () => {
| null | null
| undefined | undefined
} }
onChange={(selected) => { onChange={(selected) =>
formik.setFieldValue('flock', selected); formik.setFieldValue('flock', selected)
formik.setFieldValue('kandang', []); }
setInputValueKandang('');
}}
errorMessage={formik.errors.flock as string} errorMessage={formik.errors.flock as string}
onInputChange={setInputValueFlock} onInputChange={setInputValueFlock}
onMenuScrollToBottom={loadMoreFlock} onMenuScrollToBottom={loadMoreFlock}
@@ -691,11 +652,9 @@ const DashboardProduction = () => {
| null | null
| undefined | undefined
} }
onChange={(selected) => { onChange={(selected) =>
formik.setFieldValue('flock', selected); formik.setFieldValue('flock', selected)
formik.setFieldValue('kandang', []); }
setInputValueKandang('');
}}
errorMessage={formik.errors.flock as string} errorMessage={formik.errors.flock as string}
onInputChange={setInputValueFlock} onInputChange={setInputValueFlock}
onMenuScrollToBottom={loadMoreFlock} onMenuScrollToBottom={loadMoreFlock}
@@ -207,7 +207,7 @@ const ExpenseRealizationForm = ({
// add new realizations for each kandang // add new realizations for each kandang
kandangs.forEach((kandangItem) => { kandangs.forEach((kandangItem) => {
if (!kandangItem.id) return; if (isNaN(Number(kandangItem.id))) return;
const existingRealization = formik.values.realizations?.find( const existingRealization = formik.values.realizations?.find(
(realizationItem) => realizationItem.kandang_id === kandangItem.id (realizationItem) => realizationItem.kandang_id === kandangItem.id
@@ -35,6 +35,7 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
setInputValue: setNonstockInputValue, setInputValue: setNonstockInputValue,
options: nonstockOptions, options: nonstockOptions,
isLoadingOptions: isLoadingNonstockOptions, isLoadingOptions: isLoadingNonstockOptions,
loadMore: loadMoreNonstocks,
} = useSelect<Nonstock>( } = useSelect<Nonstock>(
NonstockApi.basePath, NonstockApi.basePath,
'id', 'id',
@@ -164,6 +165,7 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
options={nonstockOptions} options={nonstockOptions}
isLoading={isLoadingNonstockOptions} isLoading={isLoadingNonstockOptions}
onInputChange={setNonstockInputValue} onInputChange={setNonstockInputValue}
onMenuScrollToBottom={loadMoreNonstocks}
className={{ wrapper: 'min-w-48' }} className={{ wrapper: 'min-w-48' }}
isDisabled isDisabled
/> />
@@ -17,6 +17,7 @@ import {
import { MarketingFilter } from '@/types/api/marketing/marketing'; import { MarketingFilter } from '@/types/api/marketing/marketing';
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
import { MarketingApi } from '@/services/api/marketing/marketing'; import { MarketingApi } from '@/services/api/marketing/marketing';
import { CustomerApi } from '@/services/api/master-data';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { BaseMarketing, BaseSalesOrder } from '@/types/api/marketing/marketing'; import { BaseMarketing, BaseSalesOrder } from '@/types/api/marketing/marketing';
@@ -41,9 +42,12 @@ const MarketingFilterModal = ({
isLoadingOptions: isLoadingProductsOptions, isLoadingOptions: isLoadingProductsOptions,
setInputValue: setProductsInputValue, setInputValue: setProductsInputValue,
loadMore: loadMoreProducts, loadMore: loadMoreProducts,
} = useSelect<BaseMarketing>(MarketingApi.basePath, 'id', 'so_number', '', { } = useSelect<BaseMarketing>(
limit: 'limit', MarketingApi.basePath,
}); 'id',
'so_number',
'search'
);
const productsOptions = useMemo(() => { const productsOptions = useMemo(() => {
if (!productsRawData || !isResponseSuccess(productsRawData)) return []; if (!productsRawData || !isResponseSuccess(productsRawData)) return [];
@@ -70,19 +74,10 @@ const MarketingFilterModal = ({
isLoadingOptions: isLoadingCustomersOptions, isLoadingOptions: isLoadingCustomersOptions,
setInputValue: setCustomersInputValue, setInputValue: setCustomersInputValue,
loadMore: loadMoreCustomers, loadMore: loadMoreCustomers,
} = useSelect(MarketingApi.basePath, 'customer.id', 'customer.name', '', { } = useSelect(CustomerApi.basePath, 'id', 'name', 'search', {
limit: 'limit', has_marketing: 'true',
}); });
const salesCustomerOptions = useMemo(() => {
const seen = new Set<string | number>();
return customersOptions.filter((customer) => {
if (seen.has(customer.value)) return false;
seen.add(customer.value);
return true;
});
}, [customersOptions]);
const statusOptions = [ const statusOptions = [
...MARKETING_APPROVAL_LINE.map((item) => ({ ...MARKETING_APPROVAL_LINE.map((item) => ({
value: item.step_name.split(' ').join('_').toUpperCase(), value: item.step_name.split(' ').join('_').toUpperCase(),
@@ -190,7 +185,7 @@ const MarketingFilterModal = ({
label='Customer' label='Customer'
isClearable isClearable
placeholder='Pilih customer' placeholder='Pilih customer'
options={salesCustomerOptions} options={customersOptions}
isLoading={isLoadingCustomersOptions} isLoading={isLoadingCustomersOptions}
value={formik.values.customer} value={formik.values.customer}
onChange={customerChangeHandler} onChange={customerChangeHandler}
@@ -198,7 +198,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
if (filterParams.supplier_id) if (filterParams.supplier_id)
params.append('supplier_id', filterParams.supplier_id); params.append('supplier_id', filterParams.supplier_id);
if (filterParams.kandang_id) if (filterParams.kandang_id)
params.append('kandang_id', filterParams.kandang_id); params.append('project_flock_kandang_id', filterParams.kandang_id);
if (filterParams.nonstock_id) if (filterParams.nonstock_id)
params.append('nonstock_id', filterParams.nonstock_id); params.append('nonstock_id', filterParams.nonstock_id);
if (filterParams.realization_date) if (filterParams.realization_date)