mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
feat(FE-316): Add filter modal and query params for Uniformity
This commit is contained in:
@@ -23,6 +23,18 @@ import UniformityTableSkeleton from '@/components/pages/uniformity/skeleton/Unif
|
|||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { useUniformityStore } from '@/stores/uniformity/uniformity.store';
|
import { useUniformityStore } from '@/stores/uniformity/uniformity.store';
|
||||||
import FloatingActionsButton from '@/components/FloatingActionsButton';
|
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 {
|
import {
|
||||||
getStatusColor,
|
getStatusColor,
|
||||||
getStatusIndicatorColor,
|
getStatusIndicatorColor,
|
||||||
@@ -170,16 +182,170 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
const singleRejectModal = useModal();
|
const singleRejectModal = useModal();
|
||||||
const bulkApproveModal = useModal();
|
const bulkApproveModal = useModal();
|
||||||
const bulkRejectModal = 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 {
|
const {
|
||||||
data: uniformities,
|
data: uniformities,
|
||||||
isLoading,
|
isLoading,
|
||||||
mutate: refreshUniformities,
|
mutate: refreshUniformities,
|
||||||
} = useSWR(
|
} = useSWR(uniformitySwrKey, UniformityApi.getAllFetcher);
|
||||||
`${UniformityApi.basePath}${getTableFilterQueryString()}`,
|
|
||||||
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(() => {
|
const selectedRowIds = useMemo(() => {
|
||||||
return Object.keys(rowSelection)
|
return Object.keys(rowSelection)
|
||||||
.filter((key) => rowSelection[key])
|
.filter((key) => rowSelection[key])
|
||||||
@@ -485,7 +651,7 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='sm:flex gap-2'>
|
<div className='sm:flex gap-2'>
|
||||||
<Button variant='outline'>
|
<Button variant='outline' onClick={filterModal.openModal}>
|
||||||
<Icon icon='heroicons:funnel' width={18} height={18} />
|
<Icon icon='heroicons:funnel' width={18} height={18} />
|
||||||
Filter
|
Filter
|
||||||
</Button>
|
</Button>
|
||||||
@@ -683,6 +849,97 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
</div>
|
</div>
|
||||||
</ConfirmationModal>
|
</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 */}
|
{/* Floating Actions Button */}
|
||||||
<FloatingActionsButton
|
<FloatingActionsButton
|
||||||
actions={[
|
actions={[
|
||||||
|
|||||||
@@ -17,8 +17,25 @@ export class UniformityApiService extends BaseApiService<
|
|||||||
super(basePath);
|
super(basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUniformity(): Promise<BaseApiResponse<Uniformity> | undefined> {
|
async getUniformity(
|
||||||
return await this.customRequest<BaseApiResponse<Uniformity>>('');
|
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(
|
async getUniformityDetail(
|
||||||
|
|||||||
Reference in New Issue
Block a user