diff --git a/api/src/dtos/shared/base-filter.dto.ts b/api/src/dtos/shared/base-filter.dto.ts index 9b756adec9..dcdd8a02fa 100644 --- a/api/src/dtos/shared/base-filter.dto.ts +++ b/api/src/dtos/shared/base-filter.dto.ts @@ -10,6 +10,7 @@ export enum Compare { 'IN' = 'IN', '>=' = '>=', '<=' = '<=', + 'LIKE' = 'LIKE', 'NA' = 'NA', // For filters that don't use the comparison param } diff --git a/api/src/services/listing.service.ts b/api/src/services/listing.service.ts index 462af4b15e..f196b291d2 100644 --- a/api/src/services/listing.service.ts +++ b/api/src/services/listing.service.ts @@ -286,18 +286,27 @@ export class ListingService implements OnModuleInit { } if (filter[ListingFilterKeys.monthlyRent]) { const comparison = filter['$comparison']; + //sanitize user input here whereClauseArray.push( `(combined_units->>'monthlyRent')::FLOAT ${comparison} '${ filter[ListingFilterKeys.monthlyRent] }'`, ); } + if (filter[ListingFilterKeys.name]) { + const comparison = filter['$comparison']; + whereClauseArray.push( + `UPPER(combined.name) ${comparison} UPPER('%${ + filter[ListingFilterKeys.name] + }%')`, + ); + } }); } // Only return active listings whereClauseArray.push("combined.status = 'active'"); - + console.log(whereClauseArray); const whereClause = whereClauseArray?.length ? `where ${whereClauseArray.join(' AND ')}` : ''; diff --git a/doorway-ui-components/src/forms/Field.scss b/doorway-ui-components/src/forms/Field.scss index 95a3071536..bdd59c109a 100644 --- a/doorway-ui-components/src/forms/Field.scss +++ b/doorway-ui-components/src/forms/Field.scss @@ -5,7 +5,7 @@ font-size: 0.75rem; font-weight: 500; - input.rent-input { + input.typed-input { border-radius: 12px; border-width: 2px; background-color: white; diff --git a/shared-helpers/src/locales/general.json b/shared-helpers/src/locales/general.json index b71551fffa..1ab9f2e742 100644 --- a/shared-helpers/src/locales/general.json +++ b/shared-helpers/src/locales/general.json @@ -955,6 +955,8 @@ "listings.priorityUnits": "Priority Units", "listings.priorityUnitsDescription": "This building has units set aside if any of the following apply to you or someone in your household:", "listings.processInfo": "Process Info", + "listings.propertyName": "Property Name", + "listings.popertyName.helper": "Enter full or partial property name", "listings.publicLottery.header": "Public Lottery", "listings.remainingUnitsAfterPreferenceConsideration": "After all preference holders have been considered, any remaining units will be available to other qualified applicants.", "listings.remainingUnitsAfterPrograms": "One or more questions in the application will help to determine whether or not you are eligible for the housing programs listed above. After you have submitted your application, the property manager will ask you to verify your housing program eligibility by providing documentation or another form of verification.", diff --git a/shared-helpers/src/types/backend-swagger.ts b/shared-helpers/src/types/backend-swagger.ts index 9c00dbd513..2230dd03a5 100644 --- a/shared-helpers/src/types/backend-swagger.ts +++ b/shared-helpers/src/types/backend-swagger.ts @@ -6523,6 +6523,7 @@ export enum EnumListingFilterParamsComparison { "IN" = "IN", ">=" = ">=", "<=" = "<=", + "LIKE" = "LIKE", "NA" = "NA", } export enum ListingViews { @@ -6702,6 +6703,7 @@ export enum EnumMultiselectQuestionFilterParamsComparison { "IN" = "IN", ">=" = ">=", "<=" = "<=", + "LIKE" = "LIKE", "NA" = "NA", } export enum InputType { diff --git a/sites/public/src/components/listings/search/LandingSearch.module.scss b/sites/public/src/components/listings/search/LandingSearch.module.scss index 2e156e5257..badf0c82cf 100644 --- a/sites/public/src/components/listings/search/LandingSearch.module.scss +++ b/sites/public/src/components/listings/search/LandingSearch.module.scss @@ -1,7 +1,7 @@ .input-section { display: flex; flex-direction: column; - @apply p-2; + padding: var(--bloom-s4); @media (min-width: $screen-md) { flex-direction: row; } diff --git a/sites/public/src/components/listings/search/LandingSearch.tsx b/sites/public/src/components/listings/search/LandingSearch.tsx index 959d6411d2..dc42453ced 100644 --- a/sites/public/src/components/listings/search/LandingSearch.tsx +++ b/sites/public/src/components/listings/search/LandingSearch.tsx @@ -39,6 +39,7 @@ export function LandingSearch(props: LandingSearchProps) { availability: null, minRent: "", monthlyRent: "", + propertyName: "", counties: countyLabels, ids: null, } @@ -47,6 +48,7 @@ export function LandingSearch(props: LandingSearchProps) { const [openCountyMapModal, setOpenCountyMapModal] = useState(false) const createListingsUrl = (formValues: ListingSearchParams) => { + console.log(formValues) const searchUrl = buildSearchString(formValues) return "/listings?search=" + searchUrl } @@ -57,7 +59,7 @@ export function LandingSearch(props: LandingSearchProps) { Object.assign(newValues, formValues) newValues[name] = value setFormValues(newValues) - // console.log(`${name} has been set to ${value}`) // uncomment to debug + console.log(`${name} has been set to ${value}`) // uncomment to debug } const updateValueMulti = (name: string, labels: string[]) => { @@ -130,6 +132,14 @@ export function LandingSearch(props: LandingSearchProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [monthlyRentFormatted]) + const propertyName = watch("propertyName") + useEffect(() => { + if (propertyName) { + updateValue("propertyName", propertyName) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [propertyName]) + return (
@@ -170,7 +180,23 @@ export function LandingSearch(props: LandingSearchProps) { defaultValue={formValues.monthlyRent} placeholder="$" className="doorway-field p-0 md:-mt-1" - inputClassName="rent-input" + inputClassName="typed-input" + labelClassName="input-label" + /> +
+
+
{t("listings.propertyName")}
+
diff --git a/sites/public/src/components/listings/search/ListingsSearchCombined.tsx b/sites/public/src/components/listings/search/ListingsSearchCombined.tsx index 8a18c65328..c8a9ae754e 100644 --- a/sites/public/src/components/listings/search/ListingsSearchCombined.tsx +++ b/sites/public/src/components/listings/search/ListingsSearchCombined.tsx @@ -46,6 +46,7 @@ function ListingsSearchCombined(props: ListingsSearchCombinedProps) { bathrooms: null, minRent: "", monthlyRent: "", + propertyName: "", counties: props.counties.map((county) => county.label), availability: null, ids: undefined, @@ -96,6 +97,7 @@ function ListingsSearchCombined(props: ListingsSearchCombinedProps) { // Search the listings by both the filter & the visible markers - but search the markers by only the filter, so that you can scroll out of the currently searched view and still see the markers const listingIdsOnlyQb = generateSearchQuery(modifiedParams) const genericQb = generateSearchQuery(searchFilter) + console.log(genericQb) let newListings = null let newMeta diff --git a/sites/public/src/components/listings/search/ListingsSearchModal.tsx b/sites/public/src/components/listings/search/ListingsSearchModal.tsx index e0ccaa2a48..beedf18c25 100644 --- a/sites/public/src/components/listings/search/ListingsSearchModal.tsx +++ b/sites/public/src/components/listings/search/ListingsSearchModal.tsx @@ -86,6 +86,7 @@ export function ListingsSearchModal(props: ListingsSearchModalProps) { bathrooms: null, minRent: "", monthlyRent: "", + propertyName: "", counties: countyLabels, availability: null, ids: undefined, @@ -208,6 +209,7 @@ export function ListingsSearchModal(props: ListingsSearchModalProps) { const { register, getValues, setValue, watch } = useForm() const monthlyRentFormatted = watch("monthlyRent") const minRentFormatted = watch("minRent") + const propertyName = watch("propertyName") const currencyFormatting = /,|\.\d{2}/g // workarounds to leverage UI-C's currency formatting without full refactor @@ -227,6 +229,13 @@ export function ListingsSearchModal(props: ListingsSearchModalProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [monthlyRentFormatted]) + useEffect(() => { + if (propertyName) { + updateValue("propertyName", propertyName) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [propertyName]) + return (
@@ -298,11 +307,27 @@ export function ListingsSearchModal(props: ListingsSearchModalProps) { defaultValue={formValues.monthlyRent} placeholder={t("t.maxPrice")} className="doorway-field" - inputClassName="rent-input" + inputClassName="typed-input" labelClassName="input-label" >
+
+
{t("listings.propertyName")}
+ +
{t("t.counties")}
diff --git a/sites/public/src/lib/listings/listing-query-builder.ts b/sites/public/src/lib/listings/listing-query-builder.ts index e60ac13d3f..14d3f360d2 100644 --- a/sites/public/src/lib/listings/listing-query-builder.ts +++ b/sites/public/src/lib/listings/listing-query-builder.ts @@ -40,6 +40,10 @@ export class ListingQueryBuilder { return this.addFilter(field, EnumListingFilterParamsComparison["<>"], value) } + whereLike(field: string, value: string) { + return this.addFilter(field, EnumListingFilterParamsComparison["LIKE"], value) + } + whereIn(field: string, value: string[]) { return this.addFilter(field, EnumListingFilterParamsComparison["IN"], value) } diff --git a/sites/public/src/lib/listings/search.ts b/sites/public/src/lib/listings/search.ts index 4fc0765701..c9277e796b 100644 --- a/sites/public/src/lib/listings/search.ts +++ b/sites/public/src/lib/listings/search.ts @@ -6,6 +6,7 @@ export type ListingSearchParams = { bathrooms: string minRent: string monthlyRent: string + propertyName: string counties: string[] availability: FilterAvailabilityEnum ids: string[] @@ -121,6 +122,9 @@ export function generateSearchQuery(params: ListingSearchParams) { if (params.monthlyRent && params.monthlyRent != "") { qb.whereLessThanEqual("monthlyRent", params.monthlyRent) } + if (params.propertyName && params.propertyName != "") { + qb.whereLike("name", params.propertyName) + } // Find listings in these counties if (Array.isArray(params.counties) && params.counties.length > 0) {