Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group campaign filter by status #990

Merged
merged 20 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
647bef5
feat(frontend): inject @mui/material first; customize theme globally
Falinor Oct 30, 2024
e7100b0
feat(frontend): add custom campaign filter
Falinor Oct 30, 2024
9ac918a
feat(frontend): propagate the campaign filter upward
Falinor Nov 4, 2024
576c1f5
feat: share campaign status labels
Falinor Nov 4, 2024
47bf162
test(frontend): test campaign status filter
Falinor Nov 4, 2024
7ec9e39
fix(utils): fix default comparison order
Falinor Nov 5, 2024
11d4059
feat(models): compare campaign status values
Falinor Nov 5, 2024
46c48d9
build(utils): split the module in a universal and a node module
Falinor Nov 5, 2024
5896c1c
feat(frontend): order campaign statuses
Falinor Nov 5, 2024
4cc2f6b
feat(frontend): add a top border to the campaign categories
Falinor Nov 5, 2024
ab3e6e2
feat(frontend): use a different border color
Falinor Nov 5, 2024
831862d
feat(frontend): order by campaign status and creation date
Falinor Nov 5, 2024
049a76c
feat(frontend): add campaign status background color
Falinor Nov 6, 2024
f8013d5
refactor(frontend): rework campaign status selection
Falinor Nov 6, 2024
a5d27da
fix(frontend): inject DSFR z-indexes
Falinor Nov 6, 2024
cb9dd02
feat: add an option to select housings that are in no campaign
Falinor Nov 11, 2024
de90bde
feat(frontend): add top border even for the first campaign status
Falinor Nov 11, 2024
72a3fc5
test(frontend): mark a test as failing as it fails on GitHub but not …
Falinor Nov 12, 2024
1f186a3
test(frontend): remove it.failing
Falinor Nov 12, 2024
f16f6d1
test(frontend): remove problematic test
Falinor Nov 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ node_modules
build
coverage
dist
frontend-new/public/dsfr
frontend/public/dsfr
tsconfig.build.tsbuildinfo

npm-debug.log*
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"date-fns": "^2.30.0",
"fetch-intercept": "^2.4.0",
"history": "^5.3.0",
"immutable": "^5.0.0",
"jose": "^4.15.9",
"lexical": "^0.18.0",
"lodash": "^4.17.21",
Expand Down
12 changes: 5 additions & 7 deletions frontend/src/components/Campaign/CampaignStatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import AppBadge from '../_app/AppBadge/AppBadge';
import { CampaignStatus } from '@zerologementvacant/models';
import {
CAMPAIGN_STATUS_LABELS,
CampaignStatus
} from '@zerologementvacant/models';

interface Props {
status: CampaignStatus;
Expand All @@ -12,12 +15,7 @@ function CampaignStatusBadge(props: Readonly<Props>) {
'in-progress': 'green-bourgeon',
archived: 'blue-cumulus'
};
const texts = {
draft: 'Envoi en attente',
sending: 'En cours d’envoi',
'in-progress': 'Envoyée',
archived: 'Archivée'
};
const texts: Record<CampaignStatus, string> = CAMPAIGN_STATUS_LABELS;

const color = colors[props.status];
const text = texts[props.status];
Expand Down
52 changes: 22 additions & 30 deletions frontend/src/components/FiltersBadges/FiltersBadges.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import { SelectOption } from '../../models/SelectOption';
import Tag from '@codegouvfr/react-dsfr/Tag';

interface FilterBadgeProps<Value extends string> {
interface FilterBadgeProps<Value extends string | null> {
option: SelectOption;
filters: Value[] | undefined;
values: Value[] | undefined;
onChange?(value: Value[]): void;
small?: boolean;
}

function FilterBadge<Value extends string>({
option,
filters = [],
onChange,
small
}: FilterBadgeProps<Value>) {
function FilterBadge<Value extends string>(props: FilterBadgeProps<Value>) {
function onClose() {
onChange?.(filters.filter((v) => v !== option.value));
if (props.values) {
props.onChange?.(props.values.filter((v) => v !== props.option.value));
}
}

return (
<Tag
nativeButtonProps={{
onClick: onClose
}}
small={small}
dismissible={onChange !== undefined}
small={props.small}
dismissible={props.onChange !== undefined}
>
{option.badgeLabel ?? option.label}
{props.option.badgeLabel ?? props.option.label}
</Tag>
);
}

interface FilterBadgesProps<Value extends string> {
options: SelectOption<Value>[];
filters: Value[] | undefined;
values: Value[] | undefined;
onChange?(value: Value[]): void;
small?: boolean;
keepEmptyValue?: boolean;
Expand All @@ -42,36 +39,31 @@ interface FilterBadgesProps<Value extends string> {
function FilterBadges<Value extends string = string>(
props: FilterBadgesProps<Value>
) {
const { filters, onChange, options, small }: FilterBadgesProps<Value> = {
const { values, onChange, options, small }: FilterBadgesProps<Value> = {
...props,
filters: props.filters ?? []
values: props.values ?? []
};
return (
<>
{options
.filter(
(option) =>
(!!props.keepEmptyValue || !!option.value.length) &&
filters.includes(option.value)
values.includes(option.value)
)
.concat(
values
.filter(
(value) => !options.find((option) => option.value === value)
)
.map((value) => ({ value, label: value }))
)
.map((option, index) => (
<FilterBadge
option={option}
filters={filters}
onChange={onChange}
key={option + '-' + index}
small={small}
/>
))}

{filters
.filter((f) => !options.map((_) => _.value).includes(f))
.map((filter, index) => (
<FilterBadge
option={{ value: filter, label: filter }}
filters={filters}
values={values}
onChange={onChange}
key={filter + '-' + index}
key={option.value + '-' + index}
small={small}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
housingKindOptions,
localityKindsOptions,
multiOwnerOptions,
noCampaignOption,
ownerAgeOptions,
ownerKindOptions,
ownershipKindsOptions,
Expand Down Expand Up @@ -45,7 +46,7 @@ function HousingFiltersBadges(props: HousingFiltersBadgesProps) {
const establishment = useAppSelector(
(state) => state.authentication.authUser?.establishment
);
const campaignList = useCampaignList();
const campaigns = useCampaignList();
const { data: geoPerimeters } = useListGeoPerimetersQuery();
const { localitiesOptions } = useLocalityList(establishment?.id);

Expand All @@ -59,78 +60,76 @@ function HousingFiltersBadges(props: HousingFiltersBadgesProps) {
<div className="fr-tags-group">
<FilterBadges
options={allOccupancyOptions}
filters={filters.occupancies}
values={filters.occupancies}
small={small}
keepEmptyValue
onChange={(values) => onChange?.({ occupancies: values })}
/>
<FilterBadges
options={ownerKindOptions}
filters={filters.ownerKinds}
values={filters.ownerKinds}
small={small}
onChange={(values) => onChange?.({ ownerKinds: values })}
/>
<FilterBadges
options={ownerAgeOptions}
filters={filters.ownerAges}
values={filters.ownerAges}
small={small}
onChange={(values) => onChange?.({ ownerAges: values })}
/>
<FilterBadges
options={multiOwnerOptions}
filters={filters.multiOwners?.map((value) =>
value ? 'true' : 'false'
)}
values={filters.multiOwners?.map((value) => (value ? 'true' : 'false'))}
small={small}
onChange={(values) =>
onChange?.({ multiOwners: values.map((value) => value === 'true') })
}
/>
<FilterBadges
options={beneficiaryCountOptions}
filters={filters.beneficiaryCounts}
values={filters.beneficiaryCounts}
small={small}
onChange={(values) => onChange?.({ beneficiaryCounts: values })}
/>
<FilterBadges
options={housingKindOptions}
filters={filters.housingKinds}
values={filters.housingKinds}
small={small}
onChange={(values) => onChange?.({ housingKinds: values })}
/>
<FilterBadges
options={housingAreaOptions}
filters={filters.housingAreas}
values={filters.housingAreas}
small={small}
onChange={(values) => onChange?.({ housingAreas: values })}
/>
<FilterBadges
options={roomsCountOptions}
filters={filters.roomsCounts}
values={filters.roomsCounts}
small={small}
onChange={(values) => onChange?.({ roomsCounts: values })}
/>
<FilterBadges
options={cadastralClassificationOptions}
filters={filters.cadastralClassifications}
values={filters.cadastralClassifications}
small={small}
onChange={(values) => onChange?.({ cadastralClassifications: values })}
/>
<FilterBadges
options={buildingPeriodOptions}
filters={filters.buildingPeriods}
values={filters.buildingPeriods}
small={small}
onChange={(values) => onChange?.({ buildingPeriods: values })}
/>
<FilterBadges
options={vacancyYearOptions}
filters={filters.vacancyYears}
values={filters.vacancyYears}
small={small}
onChange={(values) => onChange?.({ vacancyYears: values })}
/>
<FilterBadges
options={taxedOptions}
filters={filters.isTaxedValues?.map((value) =>
values={filters.isTaxedValues?.map((value) =>
value ? 'true' : 'false'
)}
small={small}
Expand All @@ -140,38 +139,38 @@ function HousingFiltersBadges(props: HousingFiltersBadgesProps) {
/>
<FilterBadges
options={ownershipKindsOptions}
filters={filters.ownershipKinds}
values={filters.ownershipKinds}
small={small}
onChange={(values) => onChange?.({ ownershipKinds: values })}
/>
<FilterBadges
options={housingCountOptions}
filters={filters.housingCounts}
values={filters.housingCounts}
small={small}
onChange={(values) => onChange?.({ housingCounts: values })}
/>
<FilterBadges
options={vacancyRateOptions}
filters={filters.vacancyRates}
values={filters.vacancyRates}
small={small}
onChange={(values) => onChange?.({ vacancyRates: values })}
/>
<FilterBadges
options={localitiesOptions}
filters={filters.localities}
values={filters.localities}
small={small}
onChange={(values) => onChange?.({ localities: values })}
/>
<FilterBadges
options={localityKindsOptions}
filters={filters.localityKinds}
values={filters.localityKinds}
small={small}
onChange={(values) => onChange?.({ localityKinds: values })}
/>
{geoPerimeters && (
<FilterBadges
options={geoPerimeterOptions(geoPerimeters)}
filters={filters.geoPerimetersIncluded}
values={filters.geoPerimetersIncluded}
small={small}
onChange={(values) => onChange?.({ geoPerimetersIncluded: values })}
/>
Expand All @@ -182,20 +181,20 @@ function HousingFiltersBadges(props: HousingFiltersBadgesProps) {
...option,
badgeLabel: `${option.label} exclu`
}))}
filters={filters.geoPerimetersExcluded}
values={filters.geoPerimetersExcluded}
small={small}
onChange={(values) => onChange?.({ geoPerimetersExcluded: values })}
/>
)}
<FilterBadges
options={campaignsCountOptions}
filters={filters.campaignsCounts}
values={filters.campaignsCounts}
small={small}
onChange={(values) => onChange?.({ campaignsCounts: values })}
/>
<FilterBadges
options={statusOptions()}
filters={filters.statusList?.map(String)}
values={filters.statusList?.map(String)}
small={small}
onChange={(values) =>
onChange?.({
Expand All @@ -208,42 +207,52 @@ function HousingFiltersBadges(props: HousingFiltersBadgesProps) {
/>
<FilterBadges
options={getSubStatusListOptions(filters.statusList ?? [])}
filters={filters.subStatus}
values={filters.subStatus}
small={small}
onChange={(values) => onChange?.({ subStatus: values })}
/>
{campaignList && filters.campaignIds && (
{campaigns && filters.campaignIds && (
<FilterBadges
options={campaignList.map((c) => ({
value: c.id,
label: c.title
}))}
filters={filters.campaignIds}
options={campaigns
.map((campaign) => ({
value: campaign.id,
label: campaign.title
}))
.concat(noCampaignOption)}
values={filters.campaignIds.map((id) =>
id === null ? noCampaignOption.value : id
)}
small={small}
onChange={(values) => onChange?.({ campaignIds: values })}
onChange={(values) =>
onChange?.({
campaignIds: values.map((value) =>
value === noCampaignOption.value ? null : value
)
})
}
/>
)}
<FilterBadges
options={dataFileYearsIncludedOptions}
filters={filters.dataFileYearsIncluded}
values={filters.dataFileYearsIncluded}
small={small}
onChange={(values) => onChange?.({ dataFileYearsIncluded: values })}
/>
<FilterBadges
options={dataFileYearsExcludedOptions}
filters={filters.dataFileYearsExcluded}
values={filters.dataFileYearsExcluded}
small={small}
onChange={(values) => onChange?.({ dataFileYearsExcluded: values })}
/>
<FilterBadges
options={energyConsumptionOptions}
filters={filters.energyConsumption}
values={filters.energyConsumption}
small={small}
onChange={(values) => onChange?.({ energyConsumption: values })}
/>
<FilterBadges
options={[{ value: filters.query ?? '', label: filters.query ?? '' }]}
filters={filters.query ? [filters.query] : []}
values={filters.query ? [filters.query] : []}
small={small}
onChange={() => onChange?.({ query: '' })}
/>
Expand Down
Loading
Loading