mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 06:45:46 +00:00
Merge branch 'fix/adjustment-issue-13-apr-26' into 'development'
[FIX/FE] Adjustment Load More Data (useSelect Biaya), Marketing Customer Issue and Remove Fetch Kandang at Dashboard See merge request mbugroup/lti-web-client!394
This commit is contained in:
@@ -5,12 +5,12 @@ import { Icon } from '@iconify/react';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { DashboardApi } from '@/services/api/dashboard';
|
||||
import { useFormik } from 'formik';
|
||||
import { ProjectFlockApi } from '@/services/api/production';
|
||||
import { KandangApi, LocationApi } from '@/services/api/master-data';
|
||||
import { LocationApi } from '@/services/api/master-data';
|
||||
import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF';
|
||||
import {
|
||||
DashboardFilterType,
|
||||
@@ -42,6 +42,7 @@ import { cn } from '@/lib/helper';
|
||||
import DashboardExportStats, {
|
||||
DashboardExportStatsRef,
|
||||
} from '@/components/pages/dashboard/export/DashboardExportStats';
|
||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||
|
||||
// Helper function to normalize values to array
|
||||
const normalizeToArray = (
|
||||
@@ -71,6 +72,7 @@ const DashboardProduction = () => {
|
||||
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>(
|
||||
normalizeToArray(filterValues.location)
|
||||
);
|
||||
const [kandangInputValue, setRawKandangInputValue] = useState('');
|
||||
const [exporting, setExporting] = useState(false);
|
||||
const allChartsRef = useRef<DashboardExportChartsRef>(null);
|
||||
const allStatsRef = useRef<DashboardExportStatsRef>(null);
|
||||
@@ -114,23 +116,25 @@ const DashboardProduction = () => {
|
||||
options: flockOptions,
|
||||
isLoadingOptions: isLoadingFlockOptions,
|
||||
loadMore: loadMoreFlock,
|
||||
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', '', {
|
||||
location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
|
||||
});
|
||||
rawData: projectFlocksRawData,
|
||||
} = useSelect<ProjectFlock>(
|
||||
ProjectFlockApi.basePath,
|
||||
'id',
|
||||
'flock_name',
|
||||
'',
|
||||
{
|
||||
location_id:
|
||||
selectedLocationIds.length > 0 ? selectedLocationIds.toString() : '',
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
setInputValue: setInputValueLocation,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocation,
|
||||
} = useSelect(LocationApi.basePath, 'id', 'name');
|
||||
const {
|
||||
setInputValue: setInputValueKandang,
|
||||
options: kandangOptions,
|
||||
isLoadingOptions: isLoadingKandangOptions,
|
||||
loadMore: loadMoreKandang,
|
||||
} = useSelect(KandangApi.basePath, 'id', 'name', '', {
|
||||
location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
|
||||
});
|
||||
|
||||
const comparisonTypeOptions = [
|
||||
{ value: 'FARM', label: 'Farm' },
|
||||
{ value: 'FLOCK', label: 'Flock' },
|
||||
@@ -161,12 +165,68 @@ const DashboardProduction = () => {
|
||||
|
||||
const { resetForm } = formik;
|
||||
|
||||
const selectedFlockIds = useMemo(
|
||||
() => 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 {
|
||||
setInputValue: setInputValueKandang,
|
||||
options: kandangOptions,
|
||||
isLoadingOptions: isLoadingKandangOptions,
|
||||
loadMore: loadMoreKandang,
|
||||
} = kandangSelect;
|
||||
|
||||
const handleResetFilter = useCallback(() => {
|
||||
resetForm();
|
||||
resetFilterValues(); // Clear stored filter values
|
||||
setAnalysisMode('OVERVIEW');
|
||||
setSelectedLocationIds([]);
|
||||
}, [resetForm, resetFilterValues]);
|
||||
setRawKandangInputValue('');
|
||||
filterModal.closeModal();
|
||||
}, [filterModal, resetForm, resetFilterValues]);
|
||||
|
||||
// ===== Formik Error List =====
|
||||
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
||||
@@ -460,6 +520,7 @@ const DashboardProduction = () => {
|
||||
formik.setFieldValue('kandang', []);
|
||||
formik.setFieldValue('comparisonType', '');
|
||||
setSelectedLocationIds([]);
|
||||
setRawKandangInputValue('');
|
||||
}}
|
||||
color='primary'
|
||||
className={{
|
||||
@@ -505,6 +566,7 @@ const DashboardProduction = () => {
|
||||
className={{
|
||||
select: 'rounded-lg text-sm border-base-content/10',
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -530,6 +592,7 @@ const DashboardProduction = () => {
|
||||
// Reset dependent fields when location changes
|
||||
formik.setFieldValue('flock', []);
|
||||
formik.setFieldValue('kandang', []);
|
||||
setRawKandangInputValue('');
|
||||
}}
|
||||
errorMessage={formik.errors.location as string}
|
||||
options={locationOptions}
|
||||
@@ -541,6 +604,7 @@ const DashboardProduction = () => {
|
||||
className={{
|
||||
select: 'rounded-lg text-sm border-base-content/10',
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
) : (
|
||||
<SelectInputRadio
|
||||
@@ -561,6 +625,7 @@ const DashboardProduction = () => {
|
||||
// Reset dependent fields when location changes
|
||||
formik.setFieldValue('flock', []);
|
||||
formik.setFieldValue('kandang', []);
|
||||
setRawKandangInputValue('');
|
||||
}}
|
||||
errorMessage={formik.errors.location as string}
|
||||
options={locationOptions}
|
||||
@@ -572,6 +637,7 @@ const DashboardProduction = () => {
|
||||
className={{
|
||||
select: 'rounded-lg text-sm border-base-content/10',
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -596,9 +662,11 @@ const DashboardProduction = () => {
|
||||
| null
|
||||
| undefined
|
||||
}
|
||||
onChange={(selected) =>
|
||||
formik.setFieldValue('flock', selected)
|
||||
}
|
||||
onChange={(selected) => {
|
||||
formik.setFieldValue('flock', selected);
|
||||
formik.setFieldValue('kandang', []);
|
||||
setInputValueKandang('');
|
||||
}}
|
||||
errorMessage={formik.errors.flock as string}
|
||||
onInputChange={setInputValueFlock}
|
||||
onMenuScrollToBottom={loadMoreFlock}
|
||||
@@ -611,6 +679,7 @@ const DashboardProduction = () => {
|
||||
className={{
|
||||
select: 'rounded-lg text-sm border-base-content/10',
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
) : (
|
||||
<SelectInputRadio
|
||||
@@ -622,9 +691,11 @@ const DashboardProduction = () => {
|
||||
| null
|
||||
| undefined
|
||||
}
|
||||
onChange={(selected) =>
|
||||
formik.setFieldValue('flock', selected)
|
||||
}
|
||||
onChange={(selected) => {
|
||||
formik.setFieldValue('flock', selected);
|
||||
formik.setFieldValue('kandang', []);
|
||||
setInputValueKandang('');
|
||||
}}
|
||||
errorMessage={formik.errors.flock as string}
|
||||
onInputChange={setInputValueFlock}
|
||||
onMenuScrollToBottom={loadMoreFlock}
|
||||
@@ -637,6 +708,7 @@ const DashboardProduction = () => {
|
||||
className={{
|
||||
select: 'rounded-lg text-sm border-base-content/10',
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -675,6 +747,7 @@ const DashboardProduction = () => {
|
||||
className={{
|
||||
select: 'rounded-lg text-sm border-base-content/10',
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
) : (
|
||||
<SelectInputRadio
|
||||
@@ -701,6 +774,7 @@ const DashboardProduction = () => {
|
||||
className={{
|
||||
select: 'rounded-lg text-sm border-base-content/10',
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -178,12 +178,14 @@ const ExpenseRequestForm = ({
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
const {
|
||||
setInputValue: setVendorInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingVendorOptions,
|
||||
loadMore: loadMoreSuppliers,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||
|
||||
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -408,6 +410,7 @@ const ExpenseRequestForm = ({
|
||||
options={locationOptions}
|
||||
onInputChange={setLocationInputValue}
|
||||
isLoading={isLoadingLocationOptions}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
isError={
|
||||
formik.touched.location_id && Boolean(formik.errors.location_id)
|
||||
}
|
||||
@@ -452,6 +455,7 @@ const ExpenseRequestForm = ({
|
||||
options={supplierOptions}
|
||||
onInputChange={setVendorInputValue}
|
||||
isLoading={isLoadingVendorOptions}
|
||||
onMenuScrollToBottom={loadMoreSuppliers}
|
||||
isError={
|
||||
formik.touched.supplier_id && Boolean(formik.errors.supplier_id)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import SelectInput, {
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
|
||||
import {
|
||||
MarketingFilterFormValues,
|
||||
MarketingFilterSchema,
|
||||
} from '@/components/pages/marketing/filter/MarketingFilter';
|
||||
import { MarketingFilter } from '@/types/api/marketing/marketing';
|
||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||
import { MarketingApi } from '@/services/api/marketing/marketing';
|
||||
@@ -70,8 +74,8 @@ const MarketingFilterModal = ({
|
||||
limit: 'limit',
|
||||
});
|
||||
|
||||
const uniqueCustomersOptions = useMemo(() => {
|
||||
const seen = new Set();
|
||||
const salesCustomerOptions = useMemo(() => {
|
||||
const seen = new Set<string | number>();
|
||||
return customersOptions.filter((customer) => {
|
||||
if (seen.has(customer.value)) return false;
|
||||
seen.add(customer.value);
|
||||
@@ -87,23 +91,19 @@ const MarketingFilterModal = ({
|
||||
{ value: 'DITOLAK', label: 'Ditolak' },
|
||||
];
|
||||
|
||||
const formik = useFormik<{
|
||||
product_ids: OptionType[];
|
||||
status: OptionType | null;
|
||||
customer_id: OptionType | null;
|
||||
}>({
|
||||
const formik = useFormik<MarketingFilterFormValues>({
|
||||
initialValues: {
|
||||
product_ids: [],
|
||||
status: null,
|
||||
customer_id: null,
|
||||
customer: null,
|
||||
},
|
||||
validationSchema: MarketingFilterSchema,
|
||||
|
||||
onSubmit: async (values) => {
|
||||
const formattedValues = {
|
||||
...values,
|
||||
const formattedValues: MarketingFilter = {
|
||||
product_ids: values.product_ids.map((item) => Number(item.value)),
|
||||
status: values.status?.value.toString() || '',
|
||||
customer_id: Number(values.customer_id?.value),
|
||||
customer_id: Number(values.customer?.value),
|
||||
};
|
||||
|
||||
onSubmit?.(formattedValues);
|
||||
@@ -121,7 +121,10 @@ const MarketingFilterModal = ({
|
||||
};
|
||||
|
||||
const customerChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldValue('customer_id', val as OptionType);
|
||||
formik.setFieldValue(
|
||||
'customer',
|
||||
!Array.isArray(val) ? (val as OptionType<number> | null) : null
|
||||
);
|
||||
};
|
||||
|
||||
const statusChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -187,9 +190,9 @@ const MarketingFilterModal = ({
|
||||
label='Customer'
|
||||
isClearable
|
||||
placeholder='Pilih customer'
|
||||
options={uniqueCustomersOptions}
|
||||
options={salesCustomerOptions}
|
||||
isLoading={isLoadingCustomersOptions}
|
||||
value={formik.values.customer_id}
|
||||
value={formik.values.customer}
|
||||
onChange={customerChangeHandler}
|
||||
onInputChange={setCustomersInputValue}
|
||||
onMenuScrollToBottom={loadMoreCustomers}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { array, mixed, object } from 'yup';
|
||||
import { OptionType } from '@/components/input/SelectInput';
|
||||
|
||||
export const MarketingFilterSchema = object({
|
||||
product_ids: array().of(mixed<OptionType<number>>().required()).required(),
|
||||
status: mixed<OptionType<string>>().nullable(),
|
||||
customer: mixed<OptionType<number>>().nullable(),
|
||||
});
|
||||
|
||||
export type MarketingFilterFormValues = {
|
||||
product_ids: OptionType<number>[];
|
||||
status: OptionType<string> | null;
|
||||
customer: OptionType<number> | null;
|
||||
};
|
||||
Reference in New Issue
Block a user