feat(FE-316): Add filter modal and query params for Uniformity

This commit is contained in:
rstubryan
2025-12-30 10:11:51 +07:00
parent 4e5f9c710c
commit 02dc624036
2 changed files with 280 additions and 6 deletions
@@ -23,6 +23,18 @@ import UniformityTableSkeleton from '@/components/pages/uniformity/skeleton/Unif
import RequirePermission from '@/components/helper/RequirePermission';
import { useUniformityStore } from '@/stores/uniformity/uniformity.store';
import FloatingActionsButton from '@/components/FloatingActionsButton';
import Modal from '@/components/Modal';
import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import DateInput from '@/components/input/DateInput';
import { LocationApi } from '@/services/api/master-data';
import {
ProjectFlockApi,
ProjectFlockKandangApi,
} from '@/services/api/production';
import { Kandang } from '@/types/api/master-data/kandang';
import {
getStatusColor,
getStatusIndicatorColor,
@@ -170,16 +182,170 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
const singleRejectModal = useModal();
const bulkApproveModal = useModal();
const bulkRejectModal = useModal();
const filterModal = useModal();
// ===== FILTER STATE =====
const [filterLocation, setFilterLocation] = useState<OptionType | null>(null);
const [filterProjectFlock, setFilterProjectFlock] =
useState<OptionType | null>(null);
const [filterKandang, setFilterKandang] = useState<OptionType | null>(null);
const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState('');
const [projectFlockSearchValue, setProjectFlockSearchValue] = useState('');
const {
setInputValue: setFilterLocationInputValue,
options: filterLocationOptions,
isLoadingOptions: isLoadingFilterLocations,
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
// ===== FETCH PROJECT FLOCKS DATA FOR FILTER =====
const filterProjectFlocksUrl = useMemo(() => {
const params = new URLSearchParams({
search: projectFlockSearchValue || '',
limit: '100',
});
if (filterLocation) {
params.append('location_id', filterLocation.value.toString());
}
return `${ProjectFlockApi.basePath}?${params.toString()}`;
}, [projectFlockSearchValue, filterLocation]);
const {
data: filterProjectFlocksData,
isLoading: isLoadingFilterProjectFlocks,
} = useSWR(filterProjectFlocksUrl, ProjectFlockApi.getAllFetcher);
const filterProjectFlocksDataList = useMemo(
() =>
isResponseSuccess(filterProjectFlocksData)
? filterProjectFlocksData.data
: undefined,
[filterProjectFlocksData]
);
const filterProjectFlockOptions = useMemo(() => {
let options: OptionType[] = [];
if (isResponseSuccess(filterProjectFlocksData)) {
const flockOptions =
filterProjectFlocksData?.data.map((projectFlock) => ({
value: projectFlock.id,
label: projectFlock.flock_name || '',
})) || [];
options = options.concat(flockOptions);
}
return options;
}, [filterProjectFlocksData]);
// ===== KANDANG OPTIONS FOR FILTER =====
const filterKandangOptions = useMemo(() => {
let options: OptionType[] = [];
if (filterProjectFlock && filterProjectFlocksDataList) {
const selectedProjectFlockData = filterProjectFlocksDataList.find(
(pf) => pf.id === filterProjectFlock.value
);
if (selectedProjectFlockData?.kandangs) {
const kandangOpts = selectedProjectFlockData.kandangs.map(
(kandang: Kandang) => ({
value: kandang.id,
label: kandang.name || '',
})
);
options = options.concat(kandangOpts);
}
}
return options;
}, [filterProjectFlock, filterProjectFlocksDataList]);
// ===== BUILD SWR KEY WITH FILTERS =====
const uniformitySwrKey = useMemo(() => {
const basePath = UniformityApi.basePath;
const queryParams = new URLSearchParams();
if (filterLocation) {
queryParams.append('location_id', filterLocation.value.toString());
}
if (filterProjectFlock) {
queryParams.append(
'project_flock_id',
filterProjectFlock.value.toString()
);
}
if (filterKandang) {
queryParams.append('kandang_id', filterKandang.value.toString());
}
if (filterStartDate) {
queryParams.append('start_date', filterStartDate);
}
if (filterEndDate) {
queryParams.append('end_date', filterEndDate);
}
const tableQueryString = getTableFilterQueryString();
const tableParams = new URLSearchParams(
tableQueryString.split('?')[1] || ''
);
tableParams.forEach((value, key) => {
queryParams.append(key, value);
});
const queryString = queryParams.toString();
return queryString ? `${basePath}?${queryString}` : basePath;
}, [
filterLocation,
filterProjectFlock,
filterKandang,
filterStartDate,
filterEndDate,
getTableFilterQueryString,
]);
const {
data: uniformities,
isLoading,
mutate: refreshUniformities,
} = useSWR(
`${UniformityApi.basePath}${getTableFilterQueryString()}`,
UniformityApi.getAllFetcher
} = useSWR(uniformitySwrKey, UniformityApi.getAllFetcher);
// ===== FILTER HANDLERS =====
const handleFilterLocationChange = useCallback(
(val: OptionType | OptionType[] | null) => {
setFilterLocation(val as OptionType | null);
},
[]
);
const handleFilterProjectFlockChange = useCallback(
(val: OptionType | OptionType[] | null) => {
setFilterProjectFlock(val as OptionType | null);
},
[]
);
const handleFilterKandangChange = useCallback(
(val: OptionType | OptionType[] | null) => {
setFilterKandang(val as OptionType | null);
},
[]
);
const handleResetFilters = useCallback(() => {
setFilterLocation(null);
setFilterProjectFlock(null);
setFilterKandang(null);
setFilterStartDate('');
setFilterEndDate('');
}, []);
const handleApplyFilters = useCallback(() => {
filterModal.closeModal();
}, [filterModal]);
const selectedRowIds = useMemo(() => {
return Object.keys(rowSelection)
.filter((key) => rowSelection[key])
@@ -485,7 +651,7 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
</div>
<div className='sm:flex gap-2'>
<Button variant='outline'>
<Button variant='outline' onClick={filterModal.openModal}>
<Icon icon='heroicons:funnel' width={18} height={18} />
Filter
</Button>
@@ -683,6 +849,97 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
</div>
</ConfirmationModal>
{/* Filter Modal */}
<Modal
ref={filterModal.ref}
className={{ modalBox: 'rounded-2xl max-w-2xl' }}
>
<div className='flex flex-col gap-6'>
<div className='flex items-center justify-between'>
<h3 className='text-lg font-semibold'>Filter Data</h3>
<button
onClick={filterModal.closeModal}
className='p-1 hover:bg-gray-100 rounded-lg transition-colors'
>
<Icon icon='mdi:close' width={20} height={20} />
</button>
</div>
<div className='flex flex-col gap-4'>
<div className='grid grid-cols-2 gap-4'>
<DateInput
label='Tanggal Mulai'
name='start_date'
value={filterStartDate}
onChange={(e) => setFilterStartDate(e.target.value)}
className={{ wrapper: 'w-full' }}
/>
<DateInput
label='Tanggal Akhir'
name='end_date'
value={filterEndDate}
onChange={(e) => setFilterEndDate(e.target.value)}
className={{ wrapper: 'w-full' }}
/>
</div>
<SelectInput
label='Lokasi'
placeholder='Pilih Lokasi...'
value={filterLocation}
onChange={handleFilterLocationChange}
options={filterLocationOptions}
onInputChange={setFilterLocationInputValue}
isLoading={isLoadingFilterLocations}
isClearable
className={{ wrapper: 'w-full' }}
/>
<SelectInput
label='Project Flock'
placeholder='Pilih Project Flock...'
value={filterProjectFlock}
onChange={handleFilterProjectFlockChange}
options={filterProjectFlockOptions}
onInputChange={setProjectFlockSearchValue}
isLoading={isLoadingFilterProjectFlocks}
isDisabled={!filterLocation}
isClearable
className={{ wrapper: 'w-full' }}
/>
<SelectInput
label='Kandang'
placeholder='Pilih Kandang...'
value={filterKandang}
onChange={handleFilterKandangChange}
options={filterKandangOptions}
isDisabled={!filterProjectFlock}
isClearable
className={{ wrapper: 'w-full' }}
/>
</div>
<div className='flex gap-2'>
<Button
variant='outline'
className='grow'
onClick={handleResetFilters}
>
Reset
</Button>
<Button
color='primary'
className='grow'
onClick={handleApplyFilters}
>
Terapkan
</Button>
</div>
</div>
</Modal>
{/* Floating Actions Button */}
<FloatingActionsButton
actions={[
+19 -2
View File
@@ -17,8 +17,25 @@ export class UniformityApiService extends BaseApiService<
super(basePath);
}
async getUniformity(): Promise<BaseApiResponse<Uniformity> | undefined> {
return await this.customRequest<BaseApiResponse<Uniformity>>('');
async getUniformity(
location_id?: number,
project_flock_id?: number,
kandang_id?: number,
project_flock_kandang_id?: number,
start_date?: string,
end_date?: string
): Promise<BaseApiResponse<Uniformity> | undefined> {
return await this.customRequest<BaseApiResponse<Uniformity>>('', {
method: 'GET',
params: {
location_id: location_id,
project_flock_id: project_flock_id,
kandang_id: kandang_id,
project_flock_kandang_id: project_flock_kandang_id,
start_date: start_date,
end_date: end_date,
},
});
}
async getUniformityDetail(