diff --git a/src/backend/app.py b/src/backend/app.py index 5c720aa21..5234bf6e9 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -6,11 +6,12 @@ from flask import Flask, request from pages.login import Login from pages.mainpage import MainPage +from flask_cors import CORS # from logging import FileHandler, WARNING app = Flask(__name__) - +CORS(app) @app.route("/login", methods=["POST", "GET"]) def login(): @@ -80,7 +81,7 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): "params", ["num_apts", "apt_id", "search_query", "price_sort", "rating_sort"] ) param = params( - args.get("numApts", type=int), + #args.get("numApts", type=int), args.get("aptId", type=int), args.get("searchQuery", type=str), args.get("priceSort", type=int), diff --git a/src/frontend/db.json b/src/frontend/db.json index 019cc5783..ab4387524 100644 --- a/src/frontend/db.json +++ b/src/frontend/db.json @@ -1,7 +1,7 @@ { "mockdata": [ { - "id": 1, + "id": 8, "name": "Champaign park", "address": "West Green", "rating": 0, @@ -17,7 +17,7 @@ "price_max": 1000 }, { - "id": 3, + "id": 7, "name": "Town and County", "address": "Healey street", "rating": 0, @@ -49,7 +49,7 @@ "price_max": 1000 }, { - "id": 7, + "id": 3, "name": "Champaign park", "address": "Wright street", "rating": 0, @@ -57,7 +57,7 @@ "price_max": 1000 }, { - "id": 8, + "id": 2, "name": "Champaign park", "address": "West springfield", "rating": 0, diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index af3545a45..2829280f5 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -31,10 +31,12 @@ "react-router-dom": "^6.4.2", "react-scripts": "^5.0.1", "styled-components": "^5.3.6", + "underscore": "^1.13.6", "web-vitals": "^2.1.4" }, "devDependencies": { "@types/styled-components": "^5.1.26", + "@types/underscore": "^1.11.4", "prettier": "2.7.1", "typescript": "^4.8.4" } @@ -4273,6 +4275,12 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "node_modules/@types/underscore": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", + "integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -15251,6 +15259,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -19197,6 +19210,12 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@types/underscore": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", + "integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==", + "dev": true + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -27036,6 +27055,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index e1d87929f..9323c40ae 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -26,6 +26,7 @@ "react-router-dom": "^6.4.2", "react-scripts": "^5.0.1", "styled-components": "^5.3.6", + "underscore": "^1.13.6", "web-vitals": "^2.1.4" }, "scripts": { @@ -52,6 +53,7 @@ "proxy": "http://localhost:5000", "devDependencies": { "@types/styled-components": "^5.1.26", + "@types/underscore": "^1.11.4", "prettier": "2.7.1", "typescript": "^4.8.4" } diff --git a/src/frontend/src/components/SearchBar.tsx b/src/frontend/src/components/SearchBar.tsx index d535398b9..b21ba5f4d 100644 --- a/src/frontend/src/components/SearchBar.tsx +++ b/src/frontend/src/components/SearchBar.tsx @@ -1,19 +1,16 @@ import { Autocomplete, Stack, TextField } from '@mui/material'; import React, { useState } from 'react'; -import data from '../staticdata.json'; import { useSearchParams } from 'react-router-dom'; import './SearchBarStyles.css'; import getSuggestions from './getSearchBarSuggestions'; export default function SearchBar() { - // eslint-disable-next-line const [query, setQuery] = useState(''); const [searchParams, setSearchParams] = useSearchParams(); const [search, setSearch] = useState(false); - // eslint-disable-next-line const { names } = getSuggestions(query, search); - const handleSubmit = ( + const handleChange = ( event: React.SyntheticEvent, value: string ) => { @@ -39,8 +36,8 @@ export default function SearchBar() { option.name)} + onInputChange={handleChange} + options={names.map((option) => option.name)} renderInput={(params) => ( )} diff --git a/src/frontend/src/components/getApts.tsx b/src/frontend/src/components/getApts.tsx index 0a36a96dd..f0c8d351d 100644 --- a/src/frontend/src/components/getApts.tsx +++ b/src/frontend/src/components/getApts.tsx @@ -1,7 +1,12 @@ import { useState, useEffect } from 'react'; import axios from 'axios'; -function getApartments(query: string, pageNum: number, selected: string[]) { +function getApartments( + query: string, + pageNum: number, + priceSort: string[], + ratingSort: string[] +) { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const emptyarray: { @@ -19,21 +24,28 @@ function getApartments(query: string, pageNum: number, selected: string[]) { // clears the apartments setApartments(emptyarray); pageNum = 1; - }, [selected]); + }, [priceSort, ratingSort]); useEffect(() => { - // GETs new apartments whenever a button is selected + // gets new apartments on initial load and when a button is selected let limit = 2; if (query === '') { limit = 10; } + const priceNum = convertPriceSort(priceSort); + const ratingNum = convertRatingSort(ratingSort); + let populate = 'True'; + if (priceNum === 0 && ratingNum === 0) { + populate = 'False'; + } setLoading(true); setError(false); const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios({ + // http://127.0.0.1:5000/?populate=${populate}&priceSort=${priceNum}&ratingSort=${ratingNum}&numApts=${limit}&_page=${pageNum} method: 'GET', //http://localhost:3333/mockdata?q=${query}&_page=${pageNum}&_limit=2 - url: `http://localhost:3333/mockdata?q=&_page=${pageNum}&_limit=${limit}`, + url: ` http://127.0.0.1:5000/?populate=${populate}&priceSort=${priceNum}&ratingSort=${ratingNum}&numApts=${limit}`, cancelToken: source.token, }) .then((res) => { @@ -45,7 +57,7 @@ function getApartments(query: string, pageNum: number, selected: string[]) { price_max: number; }[] = []; for (let i = 0; i < res.data.length; i++) { - if (res.data[i].name !== undefined) { + if (res.data[i].name !== undefined && pageNum == 1) { newApartments.push({ name: res.data[i].name, address: res.data[i].address, @@ -68,9 +80,29 @@ function getApartments(query: string, pageNum: number, selected: string[]) { return () => { source.cancel(); }; - }, [pageNum, selected]); + }, [pageNum, priceSort, ratingSort]); return { loading, error, apartments, hasMore }; } +function convertPriceSort(sort: string[]) { + if (sort.includes('low-high')) { + return -1; + } else if (sort.includes('high-low')) { + return 1; + } else { + return 0; + } +} + +function convertRatingSort(sort: string[]) { + if (sort.includes('least popular')) { + return -1; + } else if (sort.includes('most popular')) { + return 1; + } else { + return 0; + } +} + export default getApartments; diff --git a/src/frontend/src/components/getSearchBarSuggestions.tsx b/src/frontend/src/components/getSearchBarSuggestions.tsx index ee7e55913..59906234e 100644 --- a/src/frontend/src/components/getSearchBarSuggestions.tsx +++ b/src/frontend/src/components/getSearchBarSuggestions.tsx @@ -13,12 +13,12 @@ export default function getSuggestions(query: string, search: boolean) { }, [query]); useEffect(() => { - // GETs new names whenever a button is selected + // gets new names whenever query changes const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios({ method: 'GET', - url: `http://localhost:3333/mockdata?search=${search}&searchQuery=${query}`, + url: `http://127.0.0.1:5000/?search=${search}&searchQuery=${query}`, cancelToken: source.token, }) .then((res) => { @@ -32,9 +32,6 @@ export default function getSuggestions(query: string, search: boolean) { }); } } - //setNames((prevNames) => { - // return [...new Set([...prevApartments, ...newApartments])]; - //}); setNames(newNames); }) .catch((e) => { diff --git a/src/frontend/src/components/populateLeft.tsx b/src/frontend/src/components/populateLeft.tsx index 1686bfe84..de0fa05d8 100644 --- a/src/frontend/src/components/populateLeft.tsx +++ b/src/frontend/src/components/populateLeft.tsx @@ -5,6 +5,11 @@ import { useSearchParams } from 'react-router-dom'; import './SearchBarStyles.css'; import getApartments from './getApts'; +//interface Props { +// selectedApt: number; + +//} + export default function Populate() { // eslint-disable-next-line const [query, setQuery] = useState(''); @@ -13,15 +18,16 @@ export default function Populate() { // eslint-disable-next-line const [id, setId] = useState(-1); const emptyarray: string[] = []; - const [selected, setSelected] = useState(emptyarray); + const [priceSort, setPriceSort] = useState(emptyarray); + const [ratingSort, setRatingSort] = useState(emptyarray); const { loading, error, apartments, hasMore } = getApartments( query, pageNum, - selected + priceSort, + ratingSort ); const observer = useRef(null); - // prevents the infinite scroll from triggering forever const lastAptElementRef = useCallback( (node: HTMLDivElement) => { if (loading) return; @@ -41,22 +47,13 @@ export default function Populate() { newSelected: string[] ) => { // prevents "high-low" and "low-high" from being selected at the same time - const newPrice: string[] = []; - const newPopular: string[] = []; - if (newSelected.includes('low-high')) { - newPrice.push('low-high'); - } - if (newSelected.includes('high-low')) { - newPrice.push('high-low'); - } - if (newSelected.includes('least popular')) { - newPrice.push('least popular'); - } - if (newSelected.includes('most popular')) { - newPrice.push('most popular'); + if ( + newSelected.includes('low-high') && + newSelected.includes('high-low') + ) { + newSelected.shift(); } - newSelected = [...newPrice, ...newPopular]; - setSelected(newSelected); + setPriceSort(newSelected); // sets URL if (newSelected.includes('low-high')) { searchParams.set('priceSort', '-1'); @@ -65,21 +62,39 @@ export default function Populate() { } else { searchParams.delete('priceSort'); } + searchParams.set('populate', 'True'); + if (newSelected.length == 0) { + searchParams.set('populate', 'False'); + } + setSearchParams(searchParams); + }; + + const handlePopular = ( + event: React.SyntheticEvent, + newSelected: string[] + ) => { + if ( + newSelected.includes('least popular') && + newSelected.includes('most popular') + ) { + newSelected.shift(); + } if (newSelected.includes('most popular')) { searchParams.set('ratingSort', '1'); - searchParams.set('popular', 'True'); } else if (newSelected.includes('least popular')) { searchParams.set('ratingSort', '-1'); - searchParams.set('popular', 'True'); } else { searchParams.delete('ratingSort'); - searchParams.delete('popular'); } - searchParams.set('populate', 'True'); - if (newSelected.length == 0) { + if (newSelected.length != 0) { + searchParams.set('populate', 'True'); + searchParams.set('numApts', '10'); + } else { searchParams.set('populate', 'False'); + searchParams.delete('numApts'); } setSearchParams(searchParams); + setRatingSort(newSelected); }; return ( @@ -88,12 +103,19 @@ export default function Populate() {
Low-High High-Low + + Least Popular Most Popular diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index fdacad468..fde5c4e64 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -2396,6 +2396,11 @@ "resolved" "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz" "version" "2.0.2" +"@types/underscore@^1.11.4": + "integrity" "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==" + "resolved" "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz" + "version" "1.11.4" + "@types/ws@^8.5.1": "integrity" "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==" "resolved" "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz" @@ -9139,6 +9144,11 @@ "has-symbols" "^1.0.3" "which-boxed-primitive" "^1.0.2" +"underscore@^1.13.6": + "integrity" "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + "resolved" "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz" + "version" "1.13.6" + "unicode-canonical-property-names-ecmascript@^2.0.0": "integrity" "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" "resolved" "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz"