Skip to content

Commit

Permalink
Merge pull request #1038 from MTES-MCT/feat-filter-by-epci
Browse files Browse the repository at this point in the history
Filtre par EPCI
  • Loading branch information
Falinor authored Dec 18, 2024
2 parents a2ec783 + 11d2831 commit 989a395
Show file tree
Hide file tree
Showing 49 changed files with 1,513 additions and 1,012 deletions.
37 changes: 7 additions & 30 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
# dependencies
/node_modules
/frontend/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build
/frontend/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.env
/.idea/
/frontend/.env
/dist/

# specific files
/server/routers
build/
dist/
frontend/src/components/_dsfr/
frontend/jest.polyfills.js
node_modules/
public/
tools/
Original file line number Diff line number Diff line change
@@ -1,71 +1,68 @@
import { useEffect, useState } from 'react';
import { SearchableSelect } from '../_dsfr';
import { useAvailableEstablishments } from '../../hooks/useAvailableEstablishments';
import _ from 'lodash';
import { SelectOption } from '../../models/SelectOption';
import { establishmentService } from '../../services/establishment.service';
import { useState } from 'react';
import { match, Pattern } from 'ts-pattern';

import { EstablishmentDTO } from '@zerologementvacant/models';
import { useLazyFindEstablishmentsQuery } from '../../services/establishment.service';
import SearchableSelectNext from '../SearchableSelectNext/SearchableSelectNext';
import { fr } from '@codegouvfr/react-dsfr';

interface Props {
onChange(establishmentId?: string): void;
initialEstablishmentOption?: { value: string; label: string };
className?: string;
value?: EstablishmentDTO | null;
onChange?(establishment: EstablishmentDTO | null): void;
}

const EstablishmentSearchableSelect = ({
onChange,
initialEstablishmentOption
}: Props) => {
const { availableEstablishmentOptions } = useAvailableEstablishments();
const [establishmentOptions, setEstablishmentOptions] = useState<
SelectOption[]
>([]);
const [selected, setSelected] = useState(initialEstablishmentOption?.value);

const addOption = (o1: SelectOption[], o2?: SelectOption) =>
_.unionWith(o1, o2 ? [o2] : [], (s1, s2) => s1.value === s2.value);
function EstablishmentSearchableSelect(props: Props) {
const [internalValue, setInternalValue] = useState<EstablishmentDTO | null>(
null
);
const [value, onChange] =
props.value !== undefined
? [props.value, props.onChange]
: [internalValue, setInternalValue];

useEffect(() => {
setEstablishmentOptions(
addOption(availableEstablishmentOptions, initialEstablishmentOption)
);
}, [availableEstablishmentOptions, initialEstablishmentOption]);
const [findEstablishments, { data: establishments, isFetching }] =
useLazyFindEstablishmentsQuery();

const quickSearch = (query: string) => {
if (query.length) {
establishmentService
.quickSearch(query)
.then((_) =>
setEstablishmentOptions(
_.map((establishment) => ({
value: establishment.id,
label: establishment.name
}))
)
)
.catch((err) => console.log('error', err));
} else {
setSelected(initialEstablishmentOption?.value);
setEstablishmentOptions(
addOption(availableEstablishmentOptions, initialEstablishmentOption)
);
async function search(query: string | undefined): Promise<void> {
if (query) {
await findEstablishments({ query, available: true }).unwrap();
}
};
}

return (
<SearchableSelect
className="fr-my-0 fr-mr-2w"
selected={selected}
options={establishmentOptions}
onChange={(value: any) => {
setSelected(value);
if (value.length) {
onChange(value);
<SearchableSelectNext
className={props.className}
debounce={250}
search={search}
autocompleteProps={{
autoHighlight: true,
clearIcon: null,
freeSolo: true,
getOptionKey: (option) =>
typeof option === 'string' ? option : option.id,
getOptionLabel: (option) =>
typeof option === 'string' ? option : option.name,
isOptionEqualToValue: (option, value) => option.id === value.id,
options: establishments ?? [],
loading: isFetching,
openOnFocus: true,
value: value,
onChange: (_, establishment) => {
match(establishment)
.with(Pattern.string, () => {})
.otherwise((establishment) => {
onChange?.(establishment);
});
}
}}
inputProps={{
classes: {
nativeInputOrTextArea: fr.cx('fr-my-0')
}
}}
placeholder="Rechercher un établissement"
required={true}
onTextChange={(q: string) => quickSearch(q)}
/>
);
};
}

export default EstablishmentSearchableSelect;
30 changes: 20 additions & 10 deletions frontend/src/components/Header/SmallHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { fr } from '@codegouvfr/react-dsfr';
import Button from '@codegouvfr/react-dsfr/Button';
import {
MainNavigation,
Expand All @@ -20,6 +21,11 @@ import EstablishmentSearchableSelect from '../EstablishmentSearchableSelect/Esta
import { useAppDispatch } from '../../hooks/useStore';
import logo from '../../assets/images/zlv.svg';
import { zlvApi } from '../../services/api.service';
import {
Establishment,
fromEstablishmentDTO,
toEstablishmentDTO
} from '../../models/Establishment';

function SmallHeader() {
const dispatch = useAppDispatch();
Expand All @@ -40,8 +46,10 @@ function SmallHeader() {
};
}

async function onChangeEstablishment(id: string): Promise<void> {
await dispatch(changeEstablishment(id));
async function onChangeEstablishment(
establishment: Establishment
): Promise<void> {
await dispatch(changeEstablishment(establishment.id)).unwrap();
// Reset all state instead of reloading the page
dispatch(zlvApi.util.resetApiState());
}
Expand Down Expand Up @@ -94,15 +102,17 @@ function SmallHeader() {
{isAuthenticated ? (
isAdmin || isVisitor ? (
<EstablishmentSearchableSelect
initialEstablishmentOption={
establishment
? {
value: establishment.id,
label: establishment.name
}
: undefined
className={fr.cx('fr-mr-2w')}
value={
establishment ? toEstablishmentDTO(establishment) : null
}
onChange={onChangeEstablishment}
onChange={(establishment) => {
if (establishment) {
onChangeEstablishment(
fromEstablishmentDTO(establishment)
);
}
}}
/>
) : (
<Typography component="span" mr={2} variant="body2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import {
roomsCountOptions,
statusOptions,
taxedOptions,
vacancyYearOptions,
vacancyRateOptions
vacancyRateOptions,
vacancyYearOptions
} from '../../models/HousingFilters';
import { useCampaignList } from '../../hooks/useCampaignList';
import FilterBadges from '../FiltersBadges/FiltersBadges';
Expand All @@ -34,6 +34,8 @@ import { useLocalityList } from '../../hooks/useLocalityList';
import { useAppSelector } from '../../hooks/useStore';
import { useListGeoPerimetersQuery } from '../../services/geo.service';
import fp from 'lodash/fp';
import { useIntercommunalities } from '../../hooks/useIntercommunalities';
import { SelectOption } from '../../models/SelectOption';

interface HousingFiltersBadgesProps {
filters: HousingFilters;
Expand All @@ -48,6 +50,12 @@ function HousingFiltersBadges(props: HousingFiltersBadgesProps) {
);
const campaigns = useCampaignList();
const { data: geoPerimeters } = useListGeoPerimetersQuery();
const { data: intercommunalities } = useIntercommunalities();
const intercommunalityOptions =
intercommunalities?.map<SelectOption>((intercommunality) => ({
value: intercommunality.id,
label: intercommunality.name
})) ?? [];
const { localitiesOptions } = useLocalityList(establishment?.id);

const hasFilters = fp.keys(fp.omit(['groupIds'], filters)).length > 0;
Expand Down Expand Up @@ -155,6 +163,12 @@ function HousingFiltersBadges(props: HousingFiltersBadgesProps) {
small={small}
onChange={(values) => onChange?.({ vacancyRates: values })}
/>
<FilterBadges
options={intercommunalityOptions}
values={filters.intercommunalities}
small={small}
onChange={(value) => onChange?.({ intercommunalities: value })}
/>
<FilterBadges
options={localitiesOptions}
values={filters.localities}
Expand Down
Loading

0 comments on commit 989a395

Please sign in to comment.