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
+6 -35
View File
@@ -31,6 +31,7 @@ import {
ClosingFilterType, ClosingFilterType,
} from '@/components/pages/closing/filter/ClosingFilter'; } from '@/components/pages/closing/filter/ClosingFilter';
import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton'; import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton';
import ButtonFilter from '@/components/helper/ButtonFilter';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
props, props,
@@ -287,23 +288,6 @@ const ClosingsTable = () => {
); );
}, [formik.values.project_status, projectStatusOptions]); }, [formik.values.project_status, projectStatusOptions]);
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (tableFilterState.location_id) {
count += 1;
}
if (tableFilterState.project_status) {
count += 1;
}
return count;
}, [tableFilterState.location_id, tableFilterState.project_status]);
const hasFilters = activeFiltersCount > 0;
// ===== SEARCH CHANGE HANDLER ===== // ===== SEARCH CHANGE HANDLER =====
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
updateFilter('search', e.target.value); updateFilter('search', e.target.value);
@@ -352,25 +336,12 @@ const ClosingsTable = () => {
}} }}
/> />
<Button <ButtonFilter
variant='outline' values={tableFilterState}
color='none' excludeFields={['page', 'pageSize', 'search']}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className={cn( className='px-3 py-2.5'
'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>
</div> </div>
</div> </div>
@@ -30,6 +30,7 @@ import PopoverButton from '@/components/popover/PopoverButton';
import PopoverContent from '@/components/popover/PopoverContent'; import PopoverContent from '@/components/popover/PopoverContent';
import StatusBadge from '@/components/helper/StatusBadge'; import StatusBadge from '@/components/helper/StatusBadge';
import MarketingFilterModal from '@/components/pages/marketing/MarketingFilter'; import MarketingFilterModal from '@/components/pages/marketing/MarketingFilter';
import ButtonFilter from '@/components/helper/ButtonFilter';
const RowsOptionsMenu = ({ const RowsOptionsMenu = ({
props, props,
@@ -214,32 +215,6 @@ const MarketingTable = () => {
updateFilter('customer_id', ''); updateFilter('customer_id', '');
}; };
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
// Product filter
if (tableFilterState.product_ids) {
count += 1;
}
// Status filter
if (tableFilterState.status) {
count += 1;
}
// Customer filter
if (tableFilterState.customer_id) {
count += 1;
}
return count;
}, [
tableFilterState.product_ids,
tableFilterState.status,
tableFilterState.customer_id,
]);
const approveClickHandler = () => { const approveClickHandler = () => {
setApproveAction('APPROVED'); setApproveAction('APPROVED');
confirmationModal.openModal(); confirmationModal.openModal();
@@ -588,28 +563,14 @@ const MarketingTable = () => {
)} )}
</div> </div>
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<Button <ButtonFilter
variant='outline' values={tableFilterState}
color='none' excludeFields={['page', 'pageSize', 'search']}
onClick={() => { onClick={() => {
filterModal.openModal(); filterModal.openModal();
}} }}
className={cn( className='px-3 py-2.5'
'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':
activeFiltersCount > 0,
}
)}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
Filter
{activeFiltersCount > 0 && (
<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>
<Dropdown <Dropdown
align='end' align='end'
direction='bottom' direction='bottom'
@@ -42,6 +42,7 @@ import {
} from './filter/ProjectFlockFilter'; } from './filter/ProjectFlockFilter';
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
import SelectInputRadio from '@/components/input/SelectInputRadio'; import SelectInputRadio from '@/components/input/SelectInputRadio';
import ButtonFilter from '@/components/helper/ButtonFilter';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
props, props,
@@ -346,25 +347,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
}, [formik.values.period, periodOptions]); }, [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 ===== // ===== FILTER DEPENDENCY HANDLERS =====
const handleFilterAreaChange = (area: OptionType | null) => { const handleFilterAreaChange = (area: OptionType | null) => {
const areaId = area?.value ? String(area.value) : undefined; const areaId = area?.value ? String(area.value) : undefined;
@@ -961,25 +943,12 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
}} }}
/> />
<Button <ButtonFilter
variant='outline' values={tableFilterState}
color='none' excludeFields={['page', 'pageSize', 'search']}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className={cn( className='px-3 py-2.5'
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -45,6 +45,7 @@ import StatusBadge from '@/components/helper/StatusBadge';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
import { useUiStore } from '@/stores/ui/ui.store'; import { useUiStore } from '@/stores/ui/ui.store';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
import ButtonFilter from '@/components/helper/ButtonFilter';
// ===== STATUS BADGE UTILITIES ===== // ===== STATUS BADGE UTILITIES =====
const statusTextMap: Record<string, string> = { const statusTextMap: Record<string, string> = {
@@ -511,36 +512,6 @@ const RecordingTable = () => {
); );
}, [formik.values.kandang_id, kandangOptions]); }, [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 ===== // ===== HANDLE FILTER MODAL OPEN =====
const handleFilterModalOpen = () => { const handleFilterModalOpen = () => {
filterModal.openModal(); filterModal.openModal();
@@ -1264,25 +1235,12 @@ const RecordingTable = () => {
}} }}
/> />
<Button <ButtonFilter
variant='outline' values={tableFilterState}
color='none' excludeFields={['page', 'pageSize', 'search']}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className={cn( className='px-3 py-2.5'
'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>
</div> </div>
</div> </div>
@@ -34,6 +34,7 @@ import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import ButtonFilter from '@/components/helper/ButtonFilter';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
props, props,
@@ -159,30 +160,6 @@ const TransferToLayingsTable = () => {
TransferToLayingApi.getAllFetcher 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] = const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false); useState(false);
@@ -559,30 +536,19 @@ const TransferToLayingsTable = () => {
}} }}
/> />
<Button <ButtonFilter
variant='outline' values={tableFilterState}
color='none' excludeFields={[
'page',
'pageSize',
'search',
'filter_by',
'sort_by',
]}
fieldGroups={[['startDate', 'endDate']]}
onClick={filterModal.openModal} onClick={filterModal.openModal}
className={cn( className='px-3 py-2.5'
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -48,6 +48,7 @@ import {
import { generateUniformityPDF } from '@/components/pages/production/uniformity/export/UniformityExportPDF'; import { generateUniformityPDF } from '@/components/pages/production/uniformity/export/UniformityExportPDF';
import { generateUniformityExcel } from '@/components/pages/production/uniformity/export/UniformityExportExcel'; import { generateUniformityExcel } from '@/components/pages/production/uniformity/export/UniformityExportExcel';
import Dropdown from '@/components/Dropdown'; import Dropdown from '@/components/Dropdown';
import ButtonFilter from '@/components/helper/ButtonFilter';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { import {
UniformityTableFilterSchema, UniformityTableFilterSchema,
@@ -192,16 +193,28 @@ const UniformityTable = () => {
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter,
setPage, setPage,
setPageSize,
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: '', search: '',
start_date: '',
end_date: '',
location_id: '',
project_flock_id: '',
kandang_id: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
search: 'search', 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 [filterKandang, setFilterKandang] = useState<OptionType | null>(null);
const [filterProjectFlockKandangId, setFilterProjectFlockKandangId] = const [filterProjectFlockKandangId, setFilterProjectFlockKandangId] =
useState<number | undefined>(undefined); useState<number | undefined>(undefined);
const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState('');
const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] = const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] =
useState<string>(''); useState<string>('');
const [, setFilterErrors] = useState<Record<string, string>>({}); const [, setFilterErrors] = useState<Record<string, string>>({});
@@ -319,8 +330,8 @@ const UniformityTable = () => {
// ===== FORMIK FILTER ===== // ===== FORMIK FILTER =====
const filterFormik = useFormik<UniformityTableFilterValues>({ const filterFormik = useFormik<UniformityTableFilterValues>({
initialValues: { initialValues: {
start_date: filterStartDate, start_date: tableFilterState.start_date,
end_date: filterEndDate, end_date: tableFilterState.end_date,
location: filterLocation, location: filterLocation,
project_flock: filterProjectFlock, project_flock: filterProjectFlock,
project_flock_kandang_id: filterProjectFlockKandangId, project_flock_kandang_id: filterProjectFlockKandangId,
@@ -329,8 +340,21 @@ const UniformityTable = () => {
validationSchema: UniformityTableFilterSchema, validationSchema: UniformityTableFilterSchema,
enableReinitialize: true, enableReinitialize: true,
onSubmit: async (values) => { onSubmit: async (values) => {
setFilterStartDate(values.start_date); updateFilter('start_date', values.start_date);
setFilterEndDate(values.end_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); setFilterLocation(values.location ?? null);
setFilterProjectFlock(values.project_flock ?? null); setFilterProjectFlock(values.project_flock ?? null);
setFilterKandang(values.kandang ?? null); setFilterKandang(values.kandang ?? null);
@@ -356,11 +380,11 @@ const UniformityTable = () => {
filterProjectFlockKandangId.toString() filterProjectFlockKandangId.toString()
); );
} }
if (filterStartDate) { if (tableFilterState.start_date) {
queryParams.append('start_date', filterStartDate); queryParams.append('start_date', tableFilterState.start_date);
} }
if (filterEndDate) { if (tableFilterState.end_date) {
queryParams.append('end_date', filterEndDate); queryParams.append('end_date', tableFilterState.end_date);
} }
queryParams.append('with_chart', 'true'); queryParams.append('with_chart', 'true');
} }
@@ -379,8 +403,8 @@ const UniformityTable = () => {
}, [ }, [
isSubmitted, isSubmitted,
filterProjectFlockKandangId, filterProjectFlockKandangId,
filterStartDate, tableFilterState.start_date,
filterEndDate, tableFilterState.end_date,
getTableFilterQueryString, getTableFilterQueryString,
]); ]);
@@ -456,30 +480,16 @@ const UniformityTable = () => {
setFilterProjectFlock(null); setFilterProjectFlock(null);
setFilterKandang(null); setFilterKandang(null);
setFilterProjectFlockKandangId(undefined); setFilterProjectFlockKandangId(undefined);
setFilterStartDate('');
setFilterEndDate('');
setFilterErrors({}); setFilterErrors({});
updateFilter('start_date', '');
updateFilter('end_date', '');
updateFilter('location_id', '');
updateFilter('project_flock_id', '');
updateFilter('kandang_id', '');
filterFormik.resetForm(); filterFormik.resetForm();
}, [filterFormik]); }, [filterFormik, updateFilter]);
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]
);
const selectedRowIds = useMemo(() => { const selectedRowIds = useMemo(() => {
return Object.keys(rowSelection) return Object.keys(rowSelection)
@@ -662,11 +672,11 @@ const UniformityTable = () => {
filterProjectFlockKandangId.toString() filterProjectFlockKandangId.toString()
); );
} }
if (filterStartDate) { if (tableFilterState.start_date) {
queryParams.append('start_date', filterStartDate); queryParams.append('start_date', tableFilterState.start_date);
} }
if (filterEndDate) { if (tableFilterState.end_date) {
queryParams.append('end_date', filterEndDate); queryParams.append('end_date', tableFilterState.end_date);
} }
queryParams.append('limit', '100'); queryParams.append('limit', '100');
queryParams.append('page', '1'); queryParams.append('page', '1');
@@ -677,7 +687,7 @@ const UniformityTable = () => {
const response = await UniformityApi.getAllFetcher(url); const response = await UniformityApi.getAllFetcher(url);
return isResponseSuccess(response) ? response.data : null; return isResponseSuccess(response) ? response.data : null;
}, [filterProjectFlockKandangId, filterStartDate, filterEndDate]); }, [filterProjectFlockKandangId, tableFilterState.start_date, tableFilterState.end_date]);
const handleExportExcel = useCallback(async () => { const handleExportExcel = useCallback(async () => {
setIsExcelExportLoading(true); setIsExcelExportLoading(true);
@@ -698,8 +708,8 @@ const UniformityTable = () => {
location_name: locationName, location_name: locationName,
project_flock_name: projectFlockName, project_flock_name: projectFlockName,
kandang_name: kandangName, kandang_name: kandangName,
start_date: filterStartDate, start_date: tableFilterState.start_date,
end_date: filterEndDate, end_date: tableFilterState.end_date,
}); });
toast.success('Excel berhasil dibuat dan diunduh.'); toast.success('Excel berhasil dibuat dan diunduh.');
@@ -713,8 +723,8 @@ const UniformityTable = () => {
filterLocation, filterLocation,
filterProjectFlock, filterProjectFlock,
filterKandang, filterKandang,
filterStartDate, tableFilterState.start_date,
filterEndDate, tableFilterState.end_date,
]); ]);
const handleExportPDF = useCallback(async () => { const handleExportPDF = useCallback(async () => {
@@ -736,8 +746,8 @@ const UniformityTable = () => {
location_name: locationName, location_name: locationName,
project_flock_name: projectFlockName, project_flock_name: projectFlockName,
kandang_name: kandangName, kandang_name: kandangName,
start_date: filterStartDate, start_date: tableFilterState.start_date,
end_date: filterEndDate, end_date: tableFilterState.end_date,
}); });
toast.success('PDF berhasil dibuat dan diunduh.'); toast.success('PDF berhasil dibuat dan diunduh.');
@@ -751,8 +761,8 @@ const UniformityTable = () => {
filterLocation, filterLocation,
filterProjectFlock, filterProjectFlock,
filterKandang, filterKandang,
filterStartDate, tableFilterState.start_date,
filterEndDate, tableFilterState.end_date,
]); ]);
useEffect(() => { 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 ( return (
<> <>
<div className='@container w-full'> <div className='@container w-full'>
@@ -932,30 +911,13 @@ const UniformityTable = () => {
</div> </div>
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'> <div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
<Button <ButtonFilter
variant='outline' values={tableFilterState}
color='none' excludeFields={['page', 'pageSize', 'search']}
fieldGroups={[['start_date', 'end_date']]}
onClick={filterModal.openModal} onClick={filterModal.openModal}
className={cn( className='px-3 py-2.5'
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -1279,7 +1241,7 @@ const UniformityTable = () => {
placeholder='Tanggal Mulai' placeholder='Tanggal Mulai'
value={filterFormik.values.start_date} value={filterFormik.values.start_date}
errorMessage={filterFormik.errors.start_date} errorMessage={filterFormik.errors.start_date}
onChange={handleFilterStartDateChange} onChange={(e) => filterFormik.setFieldValue('start_date', e.target.value)}
isError={ isError={
filterFormik.touched.start_date && filterFormik.touched.start_date &&
Boolean(filterFormik.errors.start_date) Boolean(filterFormik.errors.start_date)
@@ -1291,7 +1253,7 @@ const UniformityTable = () => {
placeholder='Tanggal Akhir' placeholder='Tanggal Akhir'
value={filterFormik.values.end_date} value={filterFormik.values.end_date}
errorMessage={filterFormik.errors.end_date} errorMessage={filterFormik.errors.end_date}
onChange={handleFilterEndDateChange} onChange={(e) => filterFormik.setFieldValue('end_date', e.target.value)}
isError={ isError={
filterFormik.touched.end_date && filterFormik.touched.end_date &&
Boolean(filterFormik.errors.end_date) Boolean(filterFormik.errors.end_date)
@@ -38,6 +38,7 @@ import { Nonstock } from '@/types/api/master-data/nonstock';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef } from '@tanstack/react-table';
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 ButtonFilter from '@/components/helper/ButtonFilter';
interface ReportExpenseTabProps { interface ReportExpenseTabProps {
tabId: string; tabId: string;
@@ -169,20 +170,6 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
[formik.values.category] [formik.values.category]
); );
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (filterParams.location_id) count += 1;
if (filterParams.supplier_id) count += 1;
if (filterParams.kandang_id) count += 1;
if (filterParams.nonstock_id) count += 1;
if (filterParams.realization_date) count += 1;
if (filterParams.category) count += 1;
return count;
}, [filterParams]);
const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: reportExpenseResponse, isLoading } = useSWR( const { data: reportExpenseResponse, isLoading } = useSWR(
isSubmitted isSubmitted
@@ -312,25 +299,12 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
setTabActions( setTabActions(
tabId, tabId,
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<Button <ButtonFilter
variant='outline' values={formik.values}
color='none'
onClick={() => filterModal.openModal()} onClick={() => filterModal.openModal()}
className={cn( variant='outline'
'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', className='px-3 py-2.5'
{ />
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -387,8 +361,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
); );
}, [ }, [
tabId, tabId,
hasFilters, formik.values,
activeFiltersCount,
isAnyExportLoading, isAnyExportLoading,
handleExportExcel, handleExportExcel,
handleExportPDF, handleExportPDF,
@@ -38,6 +38,7 @@ import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton'; import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
import { OptionType } from '@/components/table/TableRowSizeSelector'; import { OptionType } from '@/components/table/TableRowSizeSelector';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
import ButtonFilter from '@/components/helper/ButtonFilter';
interface CustomerPaymentTabProps { interface CustomerPaymentTabProps {
tabId: string; tabId: string;
@@ -213,30 +214,6 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
); );
}, [formik.values.filter_by]); }, [formik.values.filter_by]);
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
// Date filter (start_date + end_date = 1 filter)
if (filterParams.start_date || filterParams.end_date) {
count += 1;
}
// Customer filter
if (filterParams.customer_ids) {
count += 1;
}
// Filter by type filter (hanya dihitung jika ada nilai yang dipilih)
if (filterParams.filter_by) {
count += 1;
}
return count;
}, [filterParams]);
const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: customerPayment, isLoading } = useSWR( const { data: customerPayment, isLoading } = useSWR(
isSubmitted isSubmitted
@@ -380,25 +357,13 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
setTabActions( setTabActions(
tabId, tabId,
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<Button <ButtonFilter
variant='outline' values={formik.values}
color='none' fieldGroups={[['start_date', 'end_date']]}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className={cn( variant='outline'
'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', className='px-3 py-2.5'
{ />
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -455,8 +420,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
); );
}, [ }, [
tabId, tabId,
hasFilters, formik.values,
activeFiltersCount,
isAnyExportLoading, isAnyExportLoading,
handleExportExcel, handleExportExcel,
handleExportPdf, handleExportPdf,
@@ -274,6 +274,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<ButtonFilter <ButtonFilter
values={formik.values} values={formik.values}
fieldGroups={[['startDate', 'endDate']]}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
variant='outline' variant='outline'
className='px-3 py-2.5' className='px-3 py-2.5'
@@ -32,6 +32,7 @@ import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
import SelectInputRadio from '@/components/input/SelectInputRadio'; import SelectInputRadio from '@/components/input/SelectInputRadio';
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
import PurchasePerSupplierSkeleton from '@/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton'; import PurchasePerSupplierSkeleton from '@/components/pages/report/logistic-stock/skeleton/PurchasePerSupplierSkeleton';
import ButtonFilter from '@/components/helper/ButtonFilter';
interface PurchasesPerSupplierTabProps { interface PurchasesPerSupplierTabProps {
tabId: string; tabId: string;
@@ -253,43 +254,6 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
); );
}, [formik.values.sort_by, sortByOptions]); }, [formik.values.sort_by, sortByOptions]);
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (filterParams.start_date || filterParams.end_date) {
count += 1;
}
if (filterParams.area_id) {
count += 1;
}
if (filterParams.supplier_id) {
count += 1;
}
if (filterParams.product_id) {
count += 1;
}
if (filterParams.product_category_id) {
count += 1;
}
if (filterParams.filter_by) {
count += 1;
}
if (filterParams.sort_by) {
count += 1;
}
return count;
}, [filterParams]);
const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: purchasePerSupplier, isLoading } = useSWR( const { data: purchasePerSupplier, isLoading } = useSWR(
isSubmitted isSubmitted
@@ -486,25 +450,13 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
setTabActions( setTabActions(
tabId, tabId,
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<Button <ButtonFilter
variant='outline' values={formik.values}
color='none' fieldGroups={[['start_date', 'end_date']]}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className={cn( variant='outline'
'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', className='px-3 py-2.5'
{ />
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -561,8 +513,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
); );
}, [ }, [
tabId, tabId,
hasFilters, formik.values,
activeFiltersCount,
isAnyExportLoading, isAnyExportLoading,
filterModal.open, filterModal.open,
setTabActions, setTabActions,
@@ -47,6 +47,7 @@ import {
MARKETING_TYPE_OPTIONS, MARKETING_TYPE_OPTIONS,
} from '@/config/constant'; } from '@/config/constant';
import Badge from '@/components/Badge'; import Badge from '@/components/Badge';
import ButtonFilter from '@/components/helper/ButtonFilter';
interface DailyMarketingTabProps { interface DailyMarketingTabProps {
tabId: string; tabId: string;
@@ -202,47 +203,6 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
); );
}, [formik.values.marketing_type]); }, [formik.values.marketing_type]);
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (filterParams.area_id) {
count += 1;
}
if (filterParams.location_id) {
count += 1;
}
if (filterParams.warehouse_id) {
count += 1;
}
if (filterParams.customer_id) {
count += 1;
}
if (filterParams.start_date || filterParams.end_date) {
count += 1;
}
if (filterParams.filter_by) {
count += 1;
}
if (filterParams.marketing_type) {
count += 1;
}
if (filterParams.sort_by) {
count += 1;
}
return count;
}, [filterParams]);
const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: dailyMarketings, isLoading } = useSWR( const { data: dailyMarketings, isLoading } = useSWR(
isSubmitted isSubmitted
@@ -412,30 +372,13 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
}} }}
/> />
<Button <ButtonFilter
variant='outline' values={formik.values}
color='none' fieldGroups={[['start_date', 'end_date']]}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className={cn( variant='outline'
'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', className='px-3 py-2.5'
{ />
'border-primary-gradient text-primary': hasFilters,
}
)}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
Filter
{hasFilters && (
<Badge
className={{
badge:
'p-1.5 bg-[#FF3535] text-xs text-base-100 border border-base-300 rounded-lg',
}}
>
{activeFiltersCount}
</Badge>
)}
</Button>
<Dropdown <Dropdown
align='end' align='end'
@@ -493,8 +436,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
}, [ }, [
tabId, tabId,
searchValue, searchValue,
hasFilters, formik.values,
activeFiltersCount,
isAnyExportLoading, isAnyExportLoading,
filterModal.open, filterModal.open,
setTabActions, setTabActions,
@@ -17,6 +17,7 @@ import {
} from '@/types/api/report/hpp-per-kandang'; } from '@/types/api/report/hpp-per-kandang';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import Button from '@/components/Button'; import Button from '@/components/Button';
import ButtonFilter from '@/components/helper/ButtonFilter';
import Dropdown from '@/components/Dropdown'; import Dropdown from '@/components/Dropdown';
import { generateHppPerKandangPDF } from '@/components/pages/report/marketing/export/HppPerkandangExportPDF'; import { generateHppPerKandangPDF } from '@/components/pages/report/marketing/export/HppPerkandangExportPDF';
import { generateHppPerKandangExcel } from '@/components/pages/report/marketing/export/HppPerkandangExportXLSX'; import { generateHppPerKandangExcel } from '@/components/pages/report/marketing/export/HppPerkandangExportXLSX';
@@ -233,42 +234,6 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
); );
}, [formik.values.show_unrecorded, showUnrecordedOptions]); }, [formik.values.show_unrecorded, showUnrecordedOptions]);
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (filterParams.period) {
count += 1;
}
if (filterParams.area_id) {
count += 1;
}
if (filterParams.location_id) {
count += 1;
}
if (filterParams.kandang_id) {
count += 1;
}
if (filterParams.weight_min || filterParams.weight_max) {
count += 1;
}
if (filterParams.show_unrecorded !== undefined) {
count += 1;
}
if (filterParams.sort_by) {
count += 1;
}
return count;
}, [filterParams]);
const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: hppPerKandang, isLoading } = useSWR( const { data: hppPerKandang, isLoading } = useSWR(
@@ -486,25 +451,12 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
setTabActions( setTabActions(
tabId, tabId,
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<Button <ButtonFilter
variant='outline' values={formik.values}
color='none'
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className={cn( variant='outline'
'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', className='px-3 py-2.5'
{ />
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -561,8 +513,7 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
); );
}, [ }, [
tabId, tabId,
hasFilters, formik.values,
activeFiltersCount,
isAnyExportLoading, isAnyExportLoading,
filterModal.open, filterModal.open,
setTabActions, setTabActions,
@@ -7,6 +7,7 @@ import toast from 'react-hot-toast';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import Button from '@/components/Button'; import Button from '@/components/Button';
import ButtonFilter from '@/components/helper/ButtonFilter';
import Dropdown from '@/components/dropdown/Dropdown'; import Dropdown from '@/components/dropdown/Dropdown';
import SelectInput, { useSelect } from '@/components/input/SelectInput'; import SelectInput, { useSelect } from '@/components/input/SelectInput';
import ProductionResultProjectFlockKandangTable from '@/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTable'; import ProductionResultProjectFlockKandangTable from '@/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTable';
@@ -324,20 +325,6 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
[formik.values.kandang_id] [formik.values.kandang_id]
); );
// ===== ACTIVE FILTERS COUNT =====
const activeFiltersCount = useMemo(() => {
let count = 0;
if (filterParams.area_id) count += 1;
if (filterParams.location_id) count += 1;
if (filterParams.project_flock_id) count += 1;
if (filterParams.project_flock_kandang_id) count += 1;
return count;
}, [filterParams]);
const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: projectFlockKandangsData, isLoading } = useSWR< const { data: projectFlockKandangsData, isLoading } = useSWR<
BaseApiResponse<ProjectFlockKandang[]> BaseApiResponse<ProjectFlockKandang[]>
@@ -539,25 +526,12 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
setTabActions( setTabActions(
tabId, tabId,
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<Button <ButtonFilter
variant='outline' values={filterParams}
color='none'
onClick={() => filterModal.openModal()} onClick={() => filterModal.openModal()}
className={cn( variant='outline'
'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', className='px-3 py-2.5'
{ />
'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>
<Dropdown <Dropdown
align='end' align='end'
@@ -614,8 +588,7 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
); );
}, [ }, [
tabId, tabId,
hasFilters, filterParams,
activeFiltersCount,
isAnyExportLoading, isAnyExportLoading,
exportToExcelHandler, exportToExcelHandler,
exportToPdfHandler, exportToPdfHandler,