Skip to content

Commit

Permalink
V2 filters backend (#101)
Browse files Browse the repository at this point in the history
## Tracking Info

Resolves #100 
Resolves #70 

## Changes

<!-- What changes did you make? -->
- Added new fields for the new filters in the frontend and backend
- Integrated the v2 filters in the backend API
- Refactored frontend code
- Added FiltersConext

## Testing

<!-- How did you confirm your changes worked? -->
Manually verified the different combination of filters to make sure they
display the right results

**Note**: I haven't figured out a way to implement the multi-option
(checkbox) yet I'll add that pretty soon.

## Confirmation of Change

<!-- Upload a screenshot, if possible. Otherwise, please provide
instructions on how to see the change. -->

![image](https://github.com/TritonSE/USHS-Housing-Portal/assets/69605985/9b3c4830-5849-46d0-a2e1-da78c00ce7da)

---------

Co-authored-by: Philip Zhang <[email protected]>
  • Loading branch information
RamtinTJB and petabite authored Jun 4, 2024
1 parent aa15c0f commit 2d3228d
Show file tree
Hide file tree
Showing 12 changed files with 518 additions and 194 deletions.
105 changes: 100 additions & 5 deletions backend/src/services/units.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UpdateQuery } from "mongoose";
import { FilterQuery, UpdateQuery } from "mongoose";

import { Unit, UnitModel } from "@/models/units";

Expand All @@ -24,11 +24,23 @@ export type EditUnitBody = { dateAvailable: string } & Omit<Unit, UserReadOnlyFi
export type FilterParams = {
search?: string;
availability?: string;
housingAuthority?: string;
accessibility?: string;
rentalCriteria?: string;
additionalRules?: string;
minPrice?: string;
maxPrice?: string;
minSecurityDeposit?: string;
maxSecurityDeposit?: string;
minApplicationFee?: string;
maxApplicationFee?: string;
minSize?: string;
maxSize?: string;
fromDate?: string;
toDate?: string;
beds?: string;
baths?: string;
sort?: string;
minPrice?: string;
maxPrice?: string;
approved?: "pending" | "approved";
};

Expand Down Expand Up @@ -90,6 +102,23 @@ export const getUnits = async (filters: FilterParams) => {
const minPrice = filters.minPrice === "undefined" ? 0 : +(filters.minPrice ?? 0);
const maxPrice = filters.maxPrice === "undefined" ? 100000 : +(filters.maxPrice ?? 100000);

const minSecurityDeposit =
filters.minSecurityDeposit === "undefined" ? 0 : +(filters.minSecurityDeposit ?? 0);
const maxSecurityDeposit =
filters.maxSecurityDeposit === "undefined" ? 100000 : +(filters.maxSecurityDeposit ?? 100000);

const minApplicationFee =
filters.minApplicationFee === "undefined" ? 0 : +(filters.minApplicationFee ?? 0);
const maxApplicationFee =
filters.maxApplicationFee === "undefined" ? 100000 : +(filters.maxApplicationFee ?? 100000);

const minSize = filters.minSize === "undefined" ? 0 : +(filters.minSize ?? 0);
const maxSize = filters.maxSize === "undefined" ? 100000 : +(filters.maxSize ?? 100000);

const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
const fromDate = dateRegex.test(filters.fromDate ?? "") ? filters.fromDate : "1900-01-01";
const toDate = dateRegex.test(filters.toDate ?? "") ? filters.toDate : "2100-01-01";

const avail = filters.availability ? (filters.availability === "Available" ? true : false) : true;
const approved = filters.approved ? (filters.approved === "approved" ? true : false) : true;

Expand All @@ -115,12 +144,78 @@ export const getUnits = async (filters: FilterParams) => {
break;
}

const units = await UnitModel.find({
const accessibilityCheckboxMap = new Map<string, string>([
["First Floor", "1st floor"],
["> Second Floor", "2nd floor and above"],
["Ramps", "Ramps up to unit"],
["Stairs Only", "Stairs only"],
["Elevators", "Elevators to unit"],
]);

const rentalCriteriaCheckboxMap = new Map<string, string>([
["3rd Party Payment", "3rd party payment accepting"],
["Credit Check Required", "Credit check required"],
["Background Check Required", "Background check required"],
["Program Letter Required", "Program letter required"],
]);

const additionalRulesCheckboxMap = new Map<string, string>([
["Pets Allowed", "Pets allowed"],
["Manager On Site", "Manager on site"],
["Quiet Building", "Quiet Building"],
["Visitor Policies", "Visitor Policies"],
["Kid Friendly", "Kid friendly"],
["Min-management Interaction", "Minimal-management interaction"],
["High-management Interaction", "High-management interaction"],
]);

const hasHousingAuthority = filters.housingAuthority !== "Any";
const hasAccessibility = !(filters.accessibility === undefined || filters.accessibility === "[]");
const rentalCriteria = !(filters.rentalCriteria === undefined || filters.rentalCriteria === "[]");
const additionalRules = !(
filters.additionalRules === undefined || filters.additionalRules === "[]"
);

const query: FilterQuery<Unit> = {
numBeds: { $gte: filters.beds ?? 1 },
numBaths: { $gte: filters.baths ?? 0.5 },
monthlyRent: { $gte: minPrice, $lte: maxPrice },
securityDeposit: { $gte: minSecurityDeposit, $lte: maxSecurityDeposit },
applicationFeeCost: { $gte: minApplicationFee, $lte: maxApplicationFee },
sqft: { $gte: minSize, $lte: maxSize },
dateAvailable: { $gte: fromDate, $lte: toDate },
approved,
}).sort(sortingCriteria);
};

if (hasHousingAuthority) {
query.housingAuthority = filters.housingAuthority ?? { $exists: true };
}

if (hasAccessibility) {
query.accessibility = {
$in: (JSON.parse(filters.accessibility ?? "[]") as string[]).map((str: string) =>
accessibilityCheckboxMap.get(str),
) as string[],
};
}

if (rentalCriteria) {
query.paymentRentingCriteria = {
$in: (JSON.parse(filters.rentalCriteria ?? "[]") as string[]).map((str: string) =>
rentalCriteriaCheckboxMap.get(str),
) as string[],
};
}

if (additionalRules) {
query.additionalRules = {
$in: (JSON.parse(filters.additionalRules ?? "[]") as string[]).map((str: string) =>
additionalRulesCheckboxMap.get(str),
) as string[],
};
}

const units = await UnitModel.find(query).sort(sortingCriteria);

const filteredUnits = units.filter((unit: Unit) => {
return addressRegex.test(unit.listingAddress) && unit.availableNow === avail;
Expand Down
71 changes: 70 additions & 1 deletion frontend/src/api/units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,80 @@ export type Unit = {
updatedAt: string;
};

export const AVAILABILITY_OPTIONS = ["Available", "Leased"];
export type AvailableOptions = (typeof AVAILABILITY_OPTIONS)[number];

export const HOUSING_AUTHORITY_OPTIONS = ["Any", "LACDA", "HACLA"];
export type HousingAuthorityOptions = (typeof HOUSING_AUTHORITY_OPTIONS)[number];

export const ACCESSIBILITY_OPTIONS = [
"First Floor",
"> Second Floor",
"Stairs Only",
"Ramps",
"Elevators",
];
export type AccessibilityOptions = (typeof ACCESSIBILITY_OPTIONS)[number];

export const RENTAL_CRITERIA_OPTIONS = [
"3rd Party Payment",
"Credit Check Required",
"Background Check Required",
"Program Letter Required",
];
export type RentalCriteriaOptions = (typeof RENTAL_CRITERIA_OPTIONS)[number];

export const ADDITIONAL_RULES_OPTIONS = [
"Pets Allowed",
"Manager On Site",
"Quiet Building",
"Visitor Policies",
"Kid Friendly",
"Min-management Interaction",
"High-management Interaction",
];
export type AdditionalRulesOptions = (typeof ADDITIONAL_RULES_OPTIONS)[number];

export type FilterParams = {
search?: string;
availability?: AvailableOptions;
housingAuthority?: HousingAuthorityOptions;
accessibility?: AccessibilityOptions[];
rentalCriteria?: RentalCriteriaOptions[];
additionalRules?: AdditionalRulesOptions[];
minPrice?: number;
maxPrice?: number;
minSecurityDeposit?: number;
maxSecurityDeposit?: number;
minApplicationFee?: number;
maxApplicationFee?: number;
minSize?: number;
maxSize?: number;
fromDate?: string;
toDate?: string;
beds?: number;
baths?: number;
sort?: string;
approved?: "pending" | "approved";
};

export type GetUnitsParams = {
search?: string;
availability?: string;
housingAuthority?: string;
accessibility?: string;
rentalCriteria?: string;
additionalRules?: string;
minPrice?: string;
maxPrice?: string;
minSecurityDeposit?: string;
maxSecurityDeposit?: string;
minApplicationFee?: string;
maxApplicationFee?: string;
minSize?: string;
maxSize?: string;
fromDate?: string;
toDate?: string;
beds?: string;
baths?: string;
sort?: string;
Expand All @@ -64,7 +133,7 @@ export async function getUnit(id: string): Promise<APIResult<Unit>> {
}
}

export async function getUnits(params: FilterParams): Promise<APIResult<Unit[]>> {
export async function getUnits(params: GetUnitsParams): Promise<APIResult<Unit[]>> {
try {
const queryParams = new URLSearchParams(params);
const url = `/units?${queryParams.toString()}`;
Expand Down
35 changes: 31 additions & 4 deletions frontend/src/components/DateFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,42 @@ const FilterHeaderSpan = styled.span`
font-size: 14px;
`;

export const DateFilter = () => {
export type DateFilterValueType = {
from: string;
to: string;
};

export type DateFilterProps = {
title: string;
value: DateFilterValueType;
setValue(value: DateFilterValueType): void;
};

export const DateFilter = (props: DateFilterProps) => {
return (
<FilterContainer>
<FilterHeader title="Date Available" />
<FilterHeader title={props.title} />
<DateFilterContainer>
<FilterHeaderSpan>From</FilterHeaderSpan>
<CustomInputDate />
<CustomInputDate
value={props.value.from}
onChange={(e) => {
props.setValue({
from: e.target.value,
to: props.value.to,
});
}}
/>
<FilterHeaderSpan>To</FilterHeaderSpan>
<CustomInputDate />
<CustomInputDate
value={props.value.to}
onChange={(e) => {
props.setValue({
from: props.value.from,
to: e.target.value,
});
}}
/>
</DateFilterContainer>
</FilterContainer>
);
Expand Down
Loading

0 comments on commit 2d3228d

Please sign in to comment.