refactor(FE): Replace active filter count logic with ButtonFilter

component
This commit is contained in:
rstubryan
2026-02-25 10:39:14 +07:00
parent 47a2439777
commit f701ab0d91
13 changed files with 147 additions and 605 deletions
@@ -42,6 +42,7 @@ import {
} from './filter/ProjectFlockFilter';
import Modal from '@/components/Modal';
import SelectInputRadio from '@/components/input/SelectInputRadio';
import ButtonFilter from '@/components/helper/ButtonFilter';
const RowOptionsMenu = ({
props,
@@ -346,25 +347,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
);
}, [formik.values.period, periodOptions]);
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (tableFilterState.area_id) count += 1;
if (tableFilterState.location_id) count += 1;
if (tableFilterState.kandang_id) count += 1;
if (tableFilterState.category) count += 1;
if (tableFilterState.period) count += 1;
return count;
}, [
tableFilterState.area_id,
tableFilterState.location_id,
tableFilterState.kandang_id,
tableFilterState.category,
tableFilterState.period,
]);
const hasFilters = activeFiltersCount > 0;
// ===== FILTER DEPENDENCY HANDLERS =====
const handleFilterAreaChange = (area: OptionType | null) => {
const areaId = area?.value ? String(area.value) : undefined;
@@ -961,25 +943,12 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
}}
/>
<Button
variant='outline'
color='none'
<ButtonFilter
values={tableFilterState}
excludeFields={['page', 'pageSize', 'search']}
onClick={handleFilterModalOpen}
className={cn(
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
{
'border-primary-gradient text-primary': hasFilters,
}
)}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
Filter
{hasFilters && (
<span className='w-5 h-5 text-white bg-[#FF3535] rounded-lg border border-base-300 flex items-center justify-center text-xs'>
{activeFiltersCount}
</span>
)}
</Button>
className='px-3 py-2.5'
/>
<Dropdown
align='end'
@@ -45,6 +45,7 @@ import StatusBadge from '@/components/helper/StatusBadge';
import CheckboxInput from '@/components/input/CheckboxInput';
import { useUiStore } from '@/stores/ui/ui.store';
import { Color } from '@/types/theme';
import ButtonFilter from '@/components/helper/ButtonFilter';
// ===== STATUS BADGE UTILITIES =====
const statusTextMap: Record<string, string> = {
@@ -511,36 +512,6 @@ const RecordingTable = () => {
);
}, [formik.values.kandang_id, kandangOptions]);
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (tableFilterState.areaFilter) {
count += 1;
}
if (tableFilterState.locationFilter) {
count += 1;
}
if (tableFilterState.kandangFilter) {
count += 1;
}
if (tableFilterState.projectFlockKandangFilter) {
count += 1;
}
return count;
}, [
tableFilterState.areaFilter,
tableFilterState.locationFilter,
tableFilterState.kandangFilter,
tableFilterState.projectFlockKandangFilter,
]);
const hasFilters = activeFiltersCount > 0;
// ===== HANDLE FILTER MODAL OPEN =====
const handleFilterModalOpen = () => {
filterModal.openModal();
@@ -1264,25 +1235,12 @@ const RecordingTable = () => {
}}
/>
<Button
variant='outline'
color='none'
<ButtonFilter
values={tableFilterState}
excludeFields={['page', 'pageSize', 'search']}
onClick={handleFilterModalOpen}
className={cn(
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
{
'border-primary-gradient text-primary': hasFilters,
}
)}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
Filter
{hasFilters && (
<span className='w-5 h-5 text-white bg-[#FF3535] rounded-lg border border-base-300 flex items-center justify-center text-xs'>
{activeFiltersCount}
</span>
)}
</Button>
className='px-3 py-2.5'
/>
</div>
</div>
@@ -34,6 +34,7 @@ import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Color } from '@/types/theme';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import ButtonFilter from '@/components/helper/ButtonFilter';
const RowOptionsMenu = ({
props,
@@ -159,30 +160,6 @@ const TransferToLayingsTable = () => {
TransferToLayingApi.getAllFetcher
);
const filterCount = useMemo(() => {
let count = 0;
if (tableFilterState.startDate && tableFilterState.endDate) {
count += 1;
}
if (tableFilterState.flockSource.length > 0) {
count += 1;
}
if (tableFilterState.flockDestination.length > 0) {
count += 1;
}
if (tableFilterState.status.length > 0) {
count += 1;
}
return count;
}, [tableFilterState]);
const isFilterActive = filterCount > 0;
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
@@ -559,30 +536,19 @@ const TransferToLayingsTable = () => {
}}
/>
<Button
variant='outline'
color='none'
<ButtonFilter
values={tableFilterState}
excludeFields={[
'page',
'pageSize',
'search',
'filter_by',
'sort_by',
]}
fieldGroups={[['startDate', 'endDate']]}
onClick={filterModal.openModal}
className={cn(
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
{
'border-primary-gradient text-primary': isFilterActive,
}
)}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
Filter
{isFilterActive && (
<Badge
className={{
badge:
'p-1.5 bg-[#FF3535] text-xs text-base-100 border border-base-300 rounded-lg',
}}
>
{filterCount}
</Badge>
)}
</Button>
className='px-3 py-2.5'
/>
<Dropdown
align='end'
@@ -48,6 +48,7 @@ import {
import { generateUniformityPDF } from '@/components/pages/production/uniformity/export/UniformityExportPDF';
import { generateUniformityExcel } from '@/components/pages/production/uniformity/export/UniformityExportExcel';
import Dropdown from '@/components/Dropdown';
import ButtonFilter from '@/components/helper/ButtonFilter';
import { useFormik } from 'formik';
import {
UniformityTableFilterSchema,
@@ -192,16 +193,28 @@ const UniformityTable = () => {
const {
state: tableFilterState,
updateFilter,
setPage,
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
initial: {
search: '',
start_date: '',
end_date: '',
location_id: '',
project_flock_id: '',
kandang_id: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
search: 'search',
start_date: 'start_date',
end_date: 'end_date',
location_id: 'location_id',
project_flock_id: 'project_flock_id',
kandang_id: 'kandang_id',
},
});
@@ -233,8 +246,6 @@ const UniformityTable = () => {
const [filterKandang, setFilterKandang] = useState<OptionType | null>(null);
const [filterProjectFlockKandangId, setFilterProjectFlockKandangId] =
useState<number | undefined>(undefined);
const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState('');
const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] =
useState<string>('');
const [, setFilterErrors] = useState<Record<string, string>>({});
@@ -319,8 +330,8 @@ const UniformityTable = () => {
// ===== FORMIK FILTER =====
const filterFormik = useFormik<UniformityTableFilterValues>({
initialValues: {
start_date: filterStartDate,
end_date: filterEndDate,
start_date: tableFilterState.start_date,
end_date: tableFilterState.end_date,
location: filterLocation,
project_flock: filterProjectFlock,
project_flock_kandang_id: filterProjectFlockKandangId,
@@ -329,8 +340,21 @@ const UniformityTable = () => {
validationSchema: UniformityTableFilterSchema,
enableReinitialize: true,
onSubmit: async (values) => {
setFilterStartDate(values.start_date);
setFilterEndDate(values.end_date);
updateFilter('start_date', values.start_date);
updateFilter('end_date', values.end_date);
updateFilter(
'location_id',
values.location?.value ? String(values.location.value) : ''
);
updateFilter(
'project_flock_id',
values.project_flock?.value ? String(values.project_flock.value) : ''
);
updateFilter(
'kandang_id',
values.kandang?.value ? String(values.kandang.value) : ''
);
setFilterLocation(values.location ?? null);
setFilterProjectFlock(values.project_flock ?? null);
setFilterKandang(values.kandang ?? null);
@@ -356,11 +380,11 @@ const UniformityTable = () => {
filterProjectFlockKandangId.toString()
);
}
if (filterStartDate) {
queryParams.append('start_date', filterStartDate);
if (tableFilterState.start_date) {
queryParams.append('start_date', tableFilterState.start_date);
}
if (filterEndDate) {
queryParams.append('end_date', filterEndDate);
if (tableFilterState.end_date) {
queryParams.append('end_date', tableFilterState.end_date);
}
queryParams.append('with_chart', 'true');
}
@@ -379,8 +403,8 @@ const UniformityTable = () => {
}, [
isSubmitted,
filterProjectFlockKandangId,
filterStartDate,
filterEndDate,
tableFilterState.start_date,
tableFilterState.end_date,
getTableFilterQueryString,
]);
@@ -456,30 +480,16 @@ const UniformityTable = () => {
setFilterProjectFlock(null);
setFilterKandang(null);
setFilterProjectFlockKandangId(undefined);
setFilterStartDate('');
setFilterEndDate('');
setFilterErrors({});
updateFilter('start_date', '');
updateFilter('end_date', '');
updateFilter('location_id', '');
updateFilter('project_flock_id', '');
updateFilter('kandang_id', '');
filterFormik.resetForm();
}, [filterFormik]);
const handleFilterStartDateChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setFilterStartDate(value);
filterFormik.setFieldValue('start_date', value);
},
[filterFormik]
);
const handleFilterEndDateChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setFilterEndDate(value);
filterFormik.setFieldValue('end_date', value);
},
[filterFormik]
);
}, [filterFormik, updateFilter]);
const selectedRowIds = useMemo(() => {
return Object.keys(rowSelection)
@@ -662,11 +672,11 @@ const UniformityTable = () => {
filterProjectFlockKandangId.toString()
);
}
if (filterStartDate) {
queryParams.append('start_date', filterStartDate);
if (tableFilterState.start_date) {
queryParams.append('start_date', tableFilterState.start_date);
}
if (filterEndDate) {
queryParams.append('end_date', filterEndDate);
if (tableFilterState.end_date) {
queryParams.append('end_date', tableFilterState.end_date);
}
queryParams.append('limit', '100');
queryParams.append('page', '1');
@@ -677,7 +687,7 @@ const UniformityTable = () => {
const response = await UniformityApi.getAllFetcher(url);
return isResponseSuccess(response) ? response.data : null;
}, [filterProjectFlockKandangId, filterStartDate, filterEndDate]);
}, [filterProjectFlockKandangId, tableFilterState.start_date, tableFilterState.end_date]);
const handleExportExcel = useCallback(async () => {
setIsExcelExportLoading(true);
@@ -698,8 +708,8 @@ const UniformityTable = () => {
location_name: locationName,
project_flock_name: projectFlockName,
kandang_name: kandangName,
start_date: filterStartDate,
end_date: filterEndDate,
start_date: tableFilterState.start_date,
end_date: tableFilterState.end_date,
});
toast.success('Excel berhasil dibuat dan diunduh.');
@@ -713,8 +723,8 @@ const UniformityTable = () => {
filterLocation,
filterProjectFlock,
filterKandang,
filterStartDate,
filterEndDate,
tableFilterState.start_date,
tableFilterState.end_date,
]);
const handleExportPDF = useCallback(async () => {
@@ -736,8 +746,8 @@ const UniformityTable = () => {
location_name: locationName,
project_flock_name: projectFlockName,
kandang_name: kandangName,
start_date: filterStartDate,
end_date: filterEndDate,
start_date: tableFilterState.start_date,
end_date: tableFilterState.end_date,
});
toast.success('PDF berhasil dibuat dan diunduh.');
@@ -751,8 +761,8 @@ const UniformityTable = () => {
filterLocation,
filterProjectFlock,
filterKandang,
filterStartDate,
filterEndDate,
tableFilterState.start_date,
tableFilterState.end_date,
]);
useEffect(() => {
@@ -883,37 +893,6 @@ const UniformityTable = () => {
[]
);
// ===== CALCULATE FILTER COUNT =====
const filterCount = useMemo(() => {
let count = 0;
if (filterStartDate && filterEndDate) {
count += 1;
}
if (filterLocation) {
count += 1;
}
if (filterProjectFlock) {
count += 1;
}
if (filterKandang) {
count += 1;
}
return count;
}, [
filterStartDate,
filterEndDate,
filterLocation,
filterProjectFlock,
filterKandang,
]);
const isFilterActive = filterCount > 0;
return (
<>
<div className='@container w-full'>
@@ -932,30 +911,13 @@ const UniformityTable = () => {
</div>
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
<Button
variant='outline'
color='none'
<ButtonFilter
values={tableFilterState}
excludeFields={['page', 'pageSize', 'search']}
fieldGroups={[['start_date', 'end_date']]}
onClick={filterModal.openModal}
className={cn(
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
{
'border-primary-gradient text-primary': isFilterActive,
}
)}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
Filter
{isFilterActive && (
<Badge
className={{
badge:
'p-1.5 bg-[#FF3535] text-xs text-base-100 border border-base-300 rounded-lg',
}}
>
{filterCount}
</Badge>
)}
</Button>
className='px-3 py-2.5'
/>
<Dropdown
align='end'
@@ -1279,7 +1241,7 @@ const UniformityTable = () => {
placeholder='Tanggal Mulai'
value={filterFormik.values.start_date}
errorMessage={filterFormik.errors.start_date}
onChange={handleFilterStartDateChange}
onChange={(e) => filterFormik.setFieldValue('start_date', e.target.value)}
isError={
filterFormik.touched.start_date &&
Boolean(filterFormik.errors.start_date)
@@ -1291,7 +1253,7 @@ const UniformityTable = () => {
placeholder='Tanggal Akhir'
value={filterFormik.values.end_date}
errorMessage={filterFormik.errors.end_date}
onChange={handleFilterEndDateChange}
onChange={(e) => filterFormik.setFieldValue('end_date', e.target.value)}
isError={
filterFormik.touched.end_date &&
Boolean(filterFormik.errors.end_date)