From 709689d57b580bd8756e5d004d84cd9523f860d8 Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Sun, 5 May 2024 17:48:23 -0700 Subject: [PATCH 01/19] Added slightly buggy search bar to each dining hall page that filters each food Bug: Automatically opens the breakfast category if the searched food is in the breakfast category --- app/[dh_choice]/page.tsx | 101 +++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 15 deletions(-) diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index 021dfc1..5d0aeb2 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -1,10 +1,8 @@ "use client"; - import React, { useState, useEffect } from "react"; import axios from "axios"; import './accordian.css'; - interface Food { name: string; } @@ -21,10 +19,12 @@ interface Category { interface DiningHall { name: string; - categories: Category; + categories: Category[]; } -function name_to_dh_index(dhName: string, dhArray: Array) { + + +function name_to_dh_index(dhName: string, dhArray: DiningHall[]) { for (let i = 0; i < dhArray.length; i++) { if (dhArray[i].name === dhName) { return i; @@ -33,21 +33,25 @@ function name_to_dh_index(dhName: string, dhArray: Array) { return -1; } -function Accordion({ category, index }) { - const [isOpen, setIsOpen] = useState(false); +function Accordion({ category, index, isOpen }) { + const [isOpenState, setIsOpenState] = useState(isOpen); + + useEffect(() => { + setIsOpenState(isOpen); + }, [isOpen]); return (
- {isOpen && ( + {isOpenState && (
{category.sub_categories.map((sub_category, j) => (
@@ -65,8 +69,13 @@ function Accordion({ category, index }) { ); } + export default function Page({ searchParams }) { - const [categories, set_categories] = useState([]); + const [categories, set_categories] = useState([]); + const [searchInput, setSearchInput] = useState(''); + const [filteredFoods, setFilteredFoods] = useState([]); + const [expandedCategory, setExpandedCategory] = useState(null); + const [noFoodsFound, setNoFoodsFound] = useState(false); useEffect(() => { axios @@ -90,19 +99,81 @@ export default function Page({ searchParams }) { }); }, []); + const handleSearchInputChange = (event: React.ChangeEvent) => { + setSearchInput(event.target.value); + }; + + const handleSearch = () => { + const allFoods: Food[] = categories.reduce((accumulator: Food[], currentCategory: Category) => { + return accumulator.concat(currentCategory.sub_categories.flatMap(subCategory => subCategory.foods)); + }, []); + + const filtered = allFoods + .filter(food => food.name.toLowerCase().includes(searchInput.toLowerCase())) + .filter((value, index, self) => { + return self.findIndex(f => f.name === value.name) === index; + }); + + setFilteredFoods(filtered); + setNoFoodsFound(filtered.length === 0); + if (filtered.length > 0) { + setExpandedCategory(0); + } + }; + + function getCategoryName(food: Food): string { + for (const category of categories) { + for (const subcategory of category.sub_categories) { + if (subcategory.foods.some(f => f.name === food.name)) { + return category.name; + } + } + } + return ""; + } + return (

{searchParams.name}

+ + {/* Search bar */} +
+ + +
+ + {/* Search results */} + {filteredFoods.length > 0 && ( +
+

Search Results:

+
    + {filteredFoods.map((food, index) => ( +
  • {food.name} - {getCategoryName(food)}
  • + ))} +
+
+ )} + + {/* No foods found message */} + {noFoodsFound && ( +
+

No foods found at this dining hall.

+
+ )} + + {/* Categories */} {categories.map((category, i) => (
- {/*

{category.name}

*/} -
- -
+
))}
); -} \ No newline at end of file +} From 56c3ca271aa1f7cbd9048ff5d45ef1c1f420849d Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Sun, 5 May 2024 18:01:37 -0700 Subject: [PATCH 02/19] Updating search bar for main page, but slightly buggy Bug: Only displaying one result for some reason - i think handle search is deleting duplicates. --- app/page.tsx | 90 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 438dcc9..e7a743c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -47,49 +47,93 @@ function ButtonLink(props: any) { } function Home() { - const [dhs_names, set_dhs_names] = useState([""]); + const [dhs, setDhs] = useState([]); + const [searchInput, setSearchInput] = useState(''); + const [filteredFoods, setFilteredFoods] = useState<{ food: Food; dhName: string; categoryName: string }[]>([]); + const [showSearchResults, setShowSearchResults] = useState(false); + useEffect(() => { axios .get("http://localhost:8000/myapi/dining-halls/") .then((response) => { - // get the data from the response - const dhs: Array = response.data["locations"]; - - // print the data to the console - console.log(dhs); - - // extract the names of the dining halls - const dhs_names: string[] = []; - dhs.forEach((dh: DiningHall) => { - dhs_names.push(dh["name"]); - }); - - // set the state of the dining hall names - set_dhs_names(dhs_names); + const dhs: DiningHall[] = response.data["locations"]; + setDhs(dhs); }) .catch((error) => { console.log(error); }); }, []); + const handleSearchInputChange = (event: React.ChangeEvent) => { + setSearchInput(event.target.value); + }; + + const handleSearch = () => { + const allFoods: { food: Food; dhName: string; categoryName: string }[] = []; + dhs.forEach((dh) => { + dh.categories.forEach((category) => { + category.sub_categories.forEach((subCategory) => { + subCategory.foods.forEach((food) => { + allFoods.push({ food, dhName: dh.name, categoryName: category.name }); + }); + }); + }); + }); + + const filtered = allFoods + .filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())) + .filter(({ food }, index, self) => self.findIndex(({ food }) => food.name === food.name) === index); + + setFilteredFoods(filtered); + setShowSearchResults(true); + }; + return (
{/* Title */}

Welcome to Hungry Slugs!

- {/* Display All of the dinning hall names as links */} -
    - {dhs_names.map((dh, i) => ( -
  • - -
  • - ))} -
+ {/* Search bar */} +
+ + +
+ {/* Display search results if button clicked */} + {showSearchResults && ( +
+

Search Results:

+
    + {filteredFoods.map(({ food, dhName, categoryName }, index) => ( +
  • + {food.name} - {categoryName} ({dhName}) +
  • + ))} +
+
+ )} + {/* Display all dining halls */} +
+

Dining Halls:

+
    + {dhs.map((dh, i) => ( +
  • + +
  • + ))} +
+
); } + + export default function Page() { const [user, setUser] = useState(null); From f8fbdcef771cb9d14280ac12226a2201dc6a569f Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Sun, 5 May 2024 18:03:58 -0700 Subject: [PATCH 03/19] NVM NO LONGER BUGGY --- app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/page.tsx b/app/page.tsx index e7a743c..4873bda 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -82,7 +82,7 @@ function Home() { const filtered = allFoods .filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())) - .filter(({ food }, index, self) => self.findIndex(({ food }) => food.name === food.name) === index); + // .filter(({ food }, index, self) => self.findIndex(({ food }) => food.name === food.name) === index); setFilteredFoods(filtered); setShowSearchResults(true); From 4c5ff3d3220b33e66ad4f0739263b8a0345861ad Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Sun, 5 May 2024 18:17:50 -0700 Subject: [PATCH 04/19] Updated message for no foods found --- app/page.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/page.tsx b/app/page.tsx index 4873bda..ae7d79d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -51,6 +51,7 @@ function Home() { const [searchInput, setSearchInput] = useState(''); const [filteredFoods, setFilteredFoods] = useState<{ food: Food; dhName: string; categoryName: string }[]>([]); const [showSearchResults, setShowSearchResults] = useState(false); + const [noFoodsFound, setNoFoodsFound] = useState(false); useEffect(() => { axios @@ -84,6 +85,7 @@ function Home() { .filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())) // .filter(({ food }, index, self) => self.findIndex(({ food }) => food.name === food.name) === index); + setNoFoodsFound(filtered.length === 0); setFilteredFoods(filtered); setShowSearchResults(true); }; @@ -116,6 +118,12 @@ function Home() {
)} + + {noFoodsFound && ( +
+

No foods found at this dining hall.

+
+ )} {/* Display all dining halls */}

Dining Halls:

From df815292885326b3a2d32defd2013a8e514407ca Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Mon, 6 May 2024 09:31:38 -0700 Subject: [PATCH 05/19] Made accordian dropdown correspondant to the time of day --- app/[dh_choice]/page.tsx | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index 5d0aeb2..3da7f6a 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -22,8 +22,6 @@ interface DiningHall { categories: Category[]; } - - function name_to_dh_index(dhName: string, dhArray: DiningHall[]) { for (let i = 0; i < dhArray.length; i++) { if (dhArray[i].name === dhName) { @@ -69,7 +67,6 @@ function Accordion({ category, index, isOpen }) { ); } - export default function Page({ searchParams }) { const [categories, set_categories] = useState([]); const [searchInput, setSearchInput] = useState(''); @@ -93,12 +90,25 @@ export default function Page({ searchParams }) { alert("No food categories found"); return; } + const timeOfDay = getTimeOfDay(); + const timeIndex = a_dh.categories.findIndex(category => category.name.toLowerCase() === timeOfDay); + if (timeIndex !== -1) { + setExpandedCategory(timeIndex); + } }) .catch((error) => { console.log(error); }); }, []); + useEffect(() => { + const timeOfDay = getTimeOfDay(); + const timeIndex = categories.findIndex(category => category.name.toLowerCase() === timeOfDay); + if (timeIndex !== -1) { + setExpandedCategory(timeIndex); + } + }, [categories]); + const handleSearchInputChange = (event: React.ChangeEvent) => { setSearchInput(event.target.value); }; @@ -117,10 +127,29 @@ export default function Page({ searchParams }) { setFilteredFoods(filtered); setNoFoodsFound(filtered.length === 0); if (filtered.length > 0) { - setExpandedCategory(0); + const timeOfDay = getTimeOfDay(); + const timeIndex = categories.findIndex(category => category.name.toLowerCase() === timeOfDay); + if (timeIndex !== -1) { + setExpandedCategory(timeIndex); + } } }; + function getTimeOfDay(): string { + const currentTime = new Date(); + const currentHour = currentTime.getHours(); + + if (currentHour >= 7 && currentHour < 11) { + return "breakfast"; + } else if (currentHour >= 11 && currentHour < 16) { + return "lunch"; + } else if (currentHour >= 16 && currentHour < 19) { + return "dinner"; + } else { + return "late night"; + } + } + function getCategoryName(food: Food): string { for (const category of categories) { for (const subcategory of category.sub_categories) { @@ -177,3 +206,4 @@ export default function Page({ searchParams }) { ); } + From e7d4fecacd94dc5414fd28863f7fbf3986e68c29 Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Mon, 6 May 2024 09:41:51 -0700 Subject: [PATCH 06/19] midle of the toggle search --- app/[dh_choice]/page.tsx | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index 3da7f6a..b2b6577 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import axios from "axios"; import './accordian.css'; @@ -73,6 +73,8 @@ export default function Page({ searchParams }) { const [filteredFoods, setFilteredFoods] = useState([]); const [expandedCategory, setExpandedCategory] = useState(null); const [noFoodsFound, setNoFoodsFound] = useState(false); + const [searchActive, setSearchActive] = useState(false); + const searchRef = useRef(null); useEffect(() => { axios @@ -150,6 +152,10 @@ export default function Page({ searchParams }) { } } + const toggleSearchActive = () => { + setSearchActive(!searchActive); + }; + function getCategoryName(food: Food): string { for (const category of categories) { for (const subcategory of category.sub_categories) { @@ -161,18 +167,33 @@ export default function Page({ searchParams }) { return ""; } + useEffect(() => { + const handleOutsideClick = (event: MouseEvent) => { + if (searchRef.current && !searchRef.current.contains(event.target as Node)) { + setSearchActive(false); + } + }; + + document.addEventListener("mousedown", handleOutsideClick); + + return () => { + document.removeEventListener("mousedown", handleOutsideClick); + }; + }, []); + return (

{searchParams.name}

{/* Search bar */} -
+
@@ -205,5 +226,4 @@ export default function Page({ searchParams }) {
); -} - +} \ No newline at end of file From d94f1b32e919abbfd9f75d98a9af91d7b2534d0a Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Mon, 6 May 2024 13:08:16 -0700 Subject: [PATCH 07/19] Made global search page that works --- app/[dh_choice]/page.tsx | 7 +++ app/global_search/page.tsx | 103 +++++++++++++++++++++++++++++++++++++ components/navbar.tsx | 33 ++++++++++-- 3 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 app/global_search/page.tsx diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index b2b6577..d7c5560 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -2,6 +2,8 @@ import React, { useState, useEffect, useRef } from "react"; import axios from "axios"; import './accordian.css'; +import Link from "next/link"; + interface Food { name: string; @@ -116,6 +118,10 @@ export default function Page({ searchParams }) { }; const handleSearch = () => { + const dhChoice = searchParams.name; + const searchResultPageUrl = `/SearchResultPage`; + // Navigate to the search result page + const allFoods: Food[] = categories.reduce((accumulator: Food[], currentCategory: Category) => { return accumulator.concat(currentCategory.sub_categories.flatMap(subCategory => subCategory.foods)); }, []); @@ -194,6 +200,7 @@ export default function Page({ searchParams }) { value={searchInput} onChange={handleSearchInputChange} onClick={toggleSearchActive} + />
diff --git a/app/global_search/page.tsx b/app/global_search/page.tsx new file mode 100644 index 0000000..d200c17 --- /dev/null +++ b/app/global_search/page.tsx @@ -0,0 +1,103 @@ +"use client"; +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; + +interface Food { + name: string; +} + +interface subCategory { + name: string; + foods: Array; +} + +interface Category { + name: string; + sub_categories: Array; +} + +interface DiningHall { + name: string; + categories: Category[]; +} + +const BarebonesComponent = () => { + const [dhs, setDhs] = useState([]); + const [searchInput, setSearchInput] = useState(''); + const [filteredFoods, setFilteredFoods] = useState<{ food: Food; dhName: string; categoryName: string }[]>([]); + const [showSearchResults, setShowSearchResults] = useState(false); + const [noFoodsFound, setNoFoodsFound] = useState(false); + + useEffect(() => { + axios + .get('http://localhost:8000/myapi/dining-halls/') + .then((response) => { + const dhsData: DiningHall[] = response.data.locations; + setDhs(dhsData); + }) + .catch((error) => { + console.log(error); + }); + }, []); + + const handleSearchInputChange = (event: React.ChangeEvent) => { + setSearchInput(event.target.value); + }; + + const handleSearch = () => { + const allFoods: { food: Food; dhName: string; categoryName: string }[] = []; + dhs.forEach((dh) => { + dh.categories.forEach((category) => { + category.sub_categories.forEach((subCategory) => { + subCategory.foods.forEach((food) => { + allFoods.push({ food, dhName: dh.name, categoryName: category.name }); + }); + }); + }); + }); + + const filtered = allFoods.filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())); + + setNoFoodsFound(filtered.length === 0); + setFilteredFoods(filtered); + setShowSearchResults(true); + }; + + return ( +
+ {/* Title */} +

Welcome to Hungry Slugs!

+ {/* Search bar */} +
{/* Adjust margin as needed */} + + +
+ {/* Display search results if button clicked */} + {showSearchResults && ( +
+

Search Results:

+
    + {filteredFoods.map(({ food, dhName, categoryName }, index) => ( +
  • + {food.name} - {categoryName} ({dhName}) +
  • + ))} +
+
+ )} + + {noFoodsFound && ( +
+

No foods found at this dining hall.

+
+ )} +
+ ); +}; + +export default BarebonesComponent; diff --git a/components/navbar.tsx b/components/navbar.tsx index 1250b27..027fecd 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -2,8 +2,33 @@ export default function Navbar({ height }: { height: string }) { return ( -
-

Hungry Slugs

-
+ + + + + ); -} +} \ No newline at end of file From b5f65447ec672dc384ea8184ce6c7606ea59b987 Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Mon, 6 May 2024 14:05:54 -0700 Subject: [PATCH 08/19] updated navbar to have page for DH --- components/navbar.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/navbar.tsx b/components/navbar.tsx index 027fecd..9372a7e 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -21,6 +21,10 @@ export default function Navbar({ height }: { height: string }) {
  • Account{/* pr-X dicates how far off right we want. */}
  • +
  • + + {/* pr-X dicates how far off right we want. */} +
  • From b72a995b53927847b21a61bc6d339788f43b4dd3 Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Mon, 6 May 2024 14:13:38 -0700 Subject: [PATCH 09/19] Also made search for each dining hall, but the search needs to only look through that specific dining halls categories --- app/[dh_choice]/page.tsx | 53 +++++++++++--------- app/page.tsx | 4 +- app/search/page.tsx | 103 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 app/search/page.tsx diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index d7c5560..dedec4d 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -117,31 +117,39 @@ export default function Page({ searchParams }) { setSearchInput(event.target.value); }; - const handleSearch = () => { - const dhChoice = searchParams.name; - const searchResultPageUrl = `/SearchResultPage`; - // Navigate to the search result page + // const handleSearch = () => { + // const dhChoice = searchParams.name; + // const searchResultPageUrl = `/SearchResultPage`; + // // Navigate to the search result page - const allFoods: Food[] = categories.reduce((accumulator: Food[], currentCategory: Category) => { - return accumulator.concat(currentCategory.sub_categories.flatMap(subCategory => subCategory.foods)); - }, []); + // const allFoods: Food[] = categories.reduce((accumulator: Food[], currentCategory: Category) => { + // return accumulator.concat(currentCategory.sub_categories.flatMap(subCategory => subCategory.foods)); + // }, []); - const filtered = allFoods - .filter(food => food.name.toLowerCase().includes(searchInput.toLowerCase())) - .filter((value, index, self) => { - return self.findIndex(f => f.name === value.name) === index; - }); + // const filtered = allFoods + // .filter(food => food.name.toLowerCase().includes(searchInput.toLowerCase())) + // .filter((value, index, self) => { + // return self.findIndex(f => f.name === value.name) === index; + // }); - setFilteredFoods(filtered); - setNoFoodsFound(filtered.length === 0); - if (filtered.length > 0) { - const timeOfDay = getTimeOfDay(); - const timeIndex = categories.findIndex(category => category.name.toLowerCase() === timeOfDay); - if (timeIndex !== -1) { - setExpandedCategory(timeIndex); - } - } - }; + // setFilteredFoods(filtered); + // setNoFoodsFound(filtered.length === 0); + // if (filtered.length > 0) { + // const timeOfDay = getTimeOfDay(); + // const timeIndex = categories.findIndex(category => category.name.toLowerCase() === timeOfDay); + // if (timeIndex !== -1) { + // setExpandedCategory(timeIndex); + // } + // } + // }; + + function handleSearch() { + const dhChoice = searchParams.name; + const searchResultPageUrl = `/search?diningHall=${encodeURIComponent(searchParams.name)}`; + // Navigate to the search result page + window.location.href = searchResultPageUrl; + + } function getTimeOfDay(): string { const currentTime = new Date(); @@ -203,6 +211,7 @@ export default function Page({ searchParams }) { /> +
    {/* Search results */} diff --git a/app/page.tsx b/app/page.tsx index ae7d79d..f8f6e59 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -96,7 +96,7 @@ function Home() { {/* Title */}

    Welcome to Hungry Slugs!

    {/* Search bar */} -
    + {/*
    -
    +
    */} {/* Display search results if button clicked */} {showSearchResults && (
    diff --git a/app/search/page.tsx b/app/search/page.tsx new file mode 100644 index 0000000..d200c17 --- /dev/null +++ b/app/search/page.tsx @@ -0,0 +1,103 @@ +"use client"; +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; + +interface Food { + name: string; +} + +interface subCategory { + name: string; + foods: Array; +} + +interface Category { + name: string; + sub_categories: Array; +} + +interface DiningHall { + name: string; + categories: Category[]; +} + +const BarebonesComponent = () => { + const [dhs, setDhs] = useState([]); + const [searchInput, setSearchInput] = useState(''); + const [filteredFoods, setFilteredFoods] = useState<{ food: Food; dhName: string; categoryName: string }[]>([]); + const [showSearchResults, setShowSearchResults] = useState(false); + const [noFoodsFound, setNoFoodsFound] = useState(false); + + useEffect(() => { + axios + .get('http://localhost:8000/myapi/dining-halls/') + .then((response) => { + const dhsData: DiningHall[] = response.data.locations; + setDhs(dhsData); + }) + .catch((error) => { + console.log(error); + }); + }, []); + + const handleSearchInputChange = (event: React.ChangeEvent) => { + setSearchInput(event.target.value); + }; + + const handleSearch = () => { + const allFoods: { food: Food; dhName: string; categoryName: string }[] = []; + dhs.forEach((dh) => { + dh.categories.forEach((category) => { + category.sub_categories.forEach((subCategory) => { + subCategory.foods.forEach((food) => { + allFoods.push({ food, dhName: dh.name, categoryName: category.name }); + }); + }); + }); + }); + + const filtered = allFoods.filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())); + + setNoFoodsFound(filtered.length === 0); + setFilteredFoods(filtered); + setShowSearchResults(true); + }; + + return ( +
    + {/* Title */} +

    Welcome to Hungry Slugs!

    + {/* Search bar */} +
    {/* Adjust margin as needed */} + + +
    + {/* Display search results if button clicked */} + {showSearchResults && ( +
    +

    Search Results:

    +
      + {filteredFoods.map(({ food, dhName, categoryName }, index) => ( +
    • + {food.name} - {categoryName} ({dhName}) +
    • + ))} +
    +
    + )} + + {noFoodsFound && ( +
    +

    No foods found at this dining hall.

    +
    + )} +
    + ); +}; + +export default BarebonesComponent; From f03b562e899840f4203c6b2b2411777d08e2867a Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Mon, 6 May 2024 14:25:14 -0700 Subject: [PATCH 10/19] Finished both search features and made them independant! --- app/[dh_choice]/page.tsx | 1 + app/search/page.tsx | 41 ++++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index dedec4d..4285568 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -148,6 +148,7 @@ export default function Page({ searchParams }) { const searchResultPageUrl = `/search?diningHall=${encodeURIComponent(searchParams.name)}`; // Navigate to the search result page window.location.href = searchResultPageUrl; + localStorage.setItem('diningHall', dhChoice); } diff --git a/app/search/page.tsx b/app/search/page.tsx index d200c17..f2d1596 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -28,6 +28,8 @@ const BarebonesComponent = () => { const [showSearchResults, setShowSearchResults] = useState(false); const [noFoodsFound, setNoFoodsFound] = useState(false); + const diningHall = localStorage.getItem('diningHall'); + useEffect(() => { axios .get('http://localhost:8000/myapi/dining-halls/') @@ -45,13 +47,14 @@ const BarebonesComponent = () => { }; const handleSearch = () => { + const currentDiningHall = dhs.find((dh) => dh.name === diningHall); + if (!currentDiningHall) return; + const allFoods: { food: Food; dhName: string; categoryName: string }[] = []; - dhs.forEach((dh) => { - dh.categories.forEach((category) => { - category.sub_categories.forEach((subCategory) => { - subCategory.foods.forEach((food) => { - allFoods.push({ food, dhName: dh.name, categoryName: category.name }); - }); + currentDiningHall.categories.forEach((category) => { + category.sub_categories.forEach((subCategory) => { + subCategory.foods.forEach((food) => { + allFoods.push({ food, dhName: currentDiningHall.name, categoryName: category.name }); }); }); }); @@ -65,18 +68,20 @@ const BarebonesComponent = () => { return (
    - {/* Title */} -

    Welcome to Hungry Slugs!

    - {/* Search bar */} -
    {/* Adjust margin as needed */} - - -
    + {/* Dining Hall Name */} +

    {diningHall} Search

    + + {/* Search bar */} +
    {/* Adjust margin as needed */} + + +
    + {/* Display search results if button clicked */} {showSearchResults && (
    From 4d13fdf2e302bb15a245b32cff04a8fe762700fc Mon Sep 17 00:00:00 2001 From: Akshat Tiwari Date: Mon, 6 May 2024 15:08:01 -0700 Subject: [PATCH 11/19] Update page.tsx --- app/[dh_choice]/page.tsx | 80 +++------------------------------------- 1 file changed, 5 insertions(+), 75 deletions(-) diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index 4285568..e55a646 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -117,39 +117,12 @@ export default function Page({ searchParams }) { setSearchInput(event.target.value); }; - // const handleSearch = () => { - // const dhChoice = searchParams.name; - // const searchResultPageUrl = `/SearchResultPage`; - // // Navigate to the search result page - - // const allFoods: Food[] = categories.reduce((accumulator: Food[], currentCategory: Category) => { - // return accumulator.concat(currentCategory.sub_categories.flatMap(subCategory => subCategory.foods)); - // }, []); - - // const filtered = allFoods - // .filter(food => food.name.toLowerCase().includes(searchInput.toLowerCase())) - // .filter((value, index, self) => { - // return self.findIndex(f => f.name === value.name) === index; - // }); - - // setFilteredFoods(filtered); - // setNoFoodsFound(filtered.length === 0); - // if (filtered.length > 0) { - // const timeOfDay = getTimeOfDay(); - // const timeIndex = categories.findIndex(category => category.name.toLowerCase() === timeOfDay); - // if (timeIndex !== -1) { - // setExpandedCategory(timeIndex); - // } - // } - // }; - function handleSearch() { - const dhChoice = searchParams.name; - const searchResultPageUrl = `/search?diningHall=${encodeURIComponent(searchParams.name)}`; + const dhChoice = searchParams.name; + const searchResultPageUrl = `/search?diningHall=${encodeURIComponent(searchParams.name)}`; // Navigate to the search result page window.location.href = searchResultPageUrl; localStorage.setItem('diningHall', dhChoice); - } function getTimeOfDay(): string { @@ -167,21 +140,6 @@ export default function Page({ searchParams }) { } } - const toggleSearchActive = () => { - setSearchActive(!searchActive); - }; - - function getCategoryName(food: Food): string { - for (const category of categories) { - for (const subcategory of category.sub_categories) { - if (subcategory.foods.some(f => f.name === food.name)) { - return category.name; - } - } - } - return ""; - } - useEffect(() => { const handleOutsideClick = (event: MouseEvent) => { if (searchRef.current && !searchRef.current.contains(event.target as Node)) { @@ -201,39 +159,11 @@ export default function Page({ searchParams }) {

    {searchParams.name}

    - {/* Search bar */} -
    - + {/* Search button */} +
    -
    - - {/* Search results */} - {filteredFoods.length > 0 && ( -
    -

    Search Results:

    -
      - {filteredFoods.map((food, index) => ( -
    • {food.name} - {getCategoryName(food)}
    • - ))} -
    -
    - )} - {/* No foods found message */} - {noFoodsFound && ( -
    -

    No foods found at this dining hall.

    -
    - )} - {/* Categories */} {categories.map((category, i) => (
    @@ -243,4 +173,4 @@ export default function Page({ searchParams }) {
    ); -} \ No newline at end of file +} From 7bf8c09bb774f2996ef85274adfef6966d284c25 Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:05:31 -0700 Subject: [PATCH 12/19] locations will update on there own now --- app/[dh_choice]/page.tsx | 10 ++-- app/global_search/page.tsx | 2 +- app/page.tsx | 6 +-- app/search/page.tsx | 6 +-- backend/myapi/db_functions/dining_halls.py | 31 ----------- backend/myapi/db_functions/locations.py | 31 +++++++++++ backend/myapi/db_functions/tasks.py | 61 ++++++++++++++++++++++ backend/myapi/models.py | 32 +++++++----- backend/myapi/urls.py | 2 +- backend/myapi/views.py | 47 ++++++++++++++--- 10 files changed, 163 insertions(+), 65 deletions(-) delete mode 100644 backend/myapi/db_functions/dining_halls.py create mode 100644 backend/myapi/db_functions/locations.py create mode 100644 backend/myapi/db_functions/tasks.py diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index e41a0cd..5ee7c2d 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -81,7 +81,7 @@ export default function Page({ searchParams }) { useEffect(() => { axios - .get("http://localhost:8000/myapi/dining-halls/") + .get("http://localhost:8000/myapi/locations/") .then((response) => { const dhs = response.data.locations; const dh_index = name_to_dh_index(searchParams.name, dhs); @@ -95,7 +95,7 @@ export default function Page({ searchParams }) { alert("No food categories found"); return; } - const timeOfDay = getTimeOfDay(); + const timeOfDay = getTimeOfDay(); const timeIndex = a_dh.categories.findIndex(category => category.name.toLowerCase() === timeOfDay); if (timeIndex !== -1) { setExpandedCategory(timeIndex); @@ -109,7 +109,7 @@ export default function Page({ searchParams }) { useEffect(() => { - const timeOfDay = getTimeOfDay(); + const timeOfDay = getTimeOfDay(); const timeIndex = categories.findIndex(category => category.name.toLowerCase() === timeOfDay); if (timeIndex !== -1) { setExpandedCategory(timeIndex); @@ -131,7 +131,7 @@ export default function Page({ searchParams }) { function getTimeOfDay(): string { const currentTime = new Date(); const currentHour = currentTime.getHours(); - + if (currentHour >= 7 && currentHour < 11) { return "breakfast"; } else if (currentHour >= 11 && currentHour < 16) { @@ -161,7 +161,7 @@ export default function Page({ searchParams }) {

    {searchParams.name}

    - + {/* Search button */}
    diff --git a/app/global_search/page.tsx b/app/global_search/page.tsx index d200c17..2fb7a97 100644 --- a/app/global_search/page.tsx +++ b/app/global_search/page.tsx @@ -30,7 +30,7 @@ const BarebonesComponent = () => { useEffect(() => { axios - .get('http://localhost:8000/myapi/dining-halls/') + .get('http://localhost:8000/myapi/locations/') .then((response) => { const dhsData: DiningHall[] = response.data.locations; setDhs(dhsData); diff --git a/app/page.tsx b/app/page.tsx index 8cc2072..420c703 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -56,7 +56,7 @@ function Home() { useEffect(() => { axios - .get("http://localhost:8000/myapi/dining-halls/") + .get("http://localhost:8000/myapi/locations/") .then((response) => { const dhs: DiningHall[] = response.data["locations"]; setDhs(dhs); @@ -119,7 +119,7 @@ function Home() {
    )} - + {noFoodsFound && (

    No foods found at this dining hall.

    @@ -183,4 +183,4 @@ export default function Page() { ); -} \ No newline at end of file +} diff --git a/app/search/page.tsx b/app/search/page.tsx index f2d1596..cc85bcd 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -32,7 +32,7 @@ const BarebonesComponent = () => { useEffect(() => { axios - .get('http://localhost:8000/myapi/dining-halls/') + .get('http://localhost:8000/myapi/locations/') .then((response) => { const dhsData: DiningHall[] = response.data.locations; setDhs(dhsData); @@ -70,7 +70,7 @@ const BarebonesComponent = () => {
    {/* Dining Hall Name */}

    {diningHall} Search

    - + {/* Search bar */}
    {/* Adjust margin as needed */} { />
    - + {/* Display search results if button clicked */} {showSearchResults && (
    diff --git a/backend/myapi/db_functions/dining_halls.py b/backend/myapi/db_functions/dining_halls.py deleted file mode 100644 index f5645ae..0000000 --- a/backend/myapi/db_functions/dining_halls.py +++ /dev/null @@ -1,31 +0,0 @@ -from ..models import dining_hall_collection - - -def remove_dining_halls_from_db(names: list[str]) -> None: - for name in names: - dining_hall_collection.delete_many({"name": name}) - - -def add_dining_halls_to_db(dining_halls: list[dict]) -> None: - for dh in dining_halls: - dining_hall_collection.insert_one(dh) - - -def get_names_of_dining_halls(dining_halls: list[dict]) -> list[str]: - names = [] - for dh in dining_halls: - names.append(dh["name"]) - return names - - -def remove_add_dining_halls_to_db(dining_halls: list[dict]) -> None: - # get names of dining halls - names = get_names_of_dining_halls(dining_halls) - # remove dining halls with the names - remove_dining_halls_from_db(names) - # add dining halls to db - add_dining_halls_to_db(dining_halls) - - -def get_all_dining_halls_from_db() -> list[dict]: - return list(dining_hall_collection.find({})) diff --git a/backend/myapi/db_functions/locations.py b/backend/myapi/db_functions/locations.py new file mode 100644 index 0000000..97559e2 --- /dev/null +++ b/backend/myapi/db_functions/locations.py @@ -0,0 +1,31 @@ +from ..models import locations_collection + + +def remove_locations_from_db(names: list[str]) -> None: + for name in names: + locations_collection.delete_many({"name": name}) + + +def add_locations_to_db(locations: list[dict]) -> None: + for dh in locations: + locations_collection.insert_one(dh) + + +def get_names_of_locations(locations: list[dict]) -> list[str]: + names = [] + for dh in locations: + names.append(dh["name"]) + return names + + +def remove_add_locations_to_db(locations: list[dict]) -> None: + # get names of locations + names = get_names_of_locations(locations) + # remove locations with the names + remove_locations_from_db(names) + # add locations to db + add_locations_to_db(locations) + + +def get_all_locations_from_db() -> list[dict]: + return list(locations_collection.find({})) diff --git a/backend/myapi/db_functions/tasks.py b/backend/myapi/db_functions/tasks.py new file mode 100644 index 0000000..9ad7efc --- /dev/null +++ b/backend/myapi/db_functions/tasks.py @@ -0,0 +1,61 @@ +from celery.app import task +from ..models import tasks_collection +from datetime import datetime + +# import time from django +from django.utils import timezone + +""" +name: str +last_update: str +""" + +def set_task_last_update(task_name: str) -> None: + """ + Set the time that the task were last update the time should be based upon the database time + """ + # get the time from django + time_now: datetime = timezone.now() + + # convert the time to a string + time_str = time_now.strftime("%Y-%m-%d %H:%M:%S") + + # get the task from the tasks collection + task: dict | None = tasks_collection.find_one({"name": task_name}) + + # check if the task exists + if task is None: + # create a new task + task = { + "name": task_name, + "last_update": time_str + } + # insert the task into the tasks collection + tasks_collection.insert_one(task) + else: + # update the task + tasks_collection.update_one( + {"name": task_name}, + {"$set": {"last_update": time_str}} + ) + +def get_task_last_update(task_name: str) -> datetime | None: + """ + Get the last update time of the task from the database + """ + # find the task in the tasks collection + task: dict | None = tasks_collection.find_one({"name": task_name}) + + # check if the location exists + if task is None: + return None + + # get the last update time + last_update = task["last_update"] + + # convert the string to a datetime object + last_update_time = datetime.strptime(last_update, "%Y-%m-%d %H:%M:%S") + + return last_update_time + + diff --git a/backend/myapi/models.py b/backend/myapi/models.py index cf19c06..5fad088 100644 --- a/backend/myapi/models.py +++ b/backend/myapi/models.py @@ -1,26 +1,32 @@ from django.db import models +from django.conf.locale import ta +from celery.app import task +from requests import get from db_connection import db # Create your models here. -# Dining Hall Model -dining_hall_collection = db["dining_hall"] +# locations Model +locations_collection = db["locations"] -# Food Rating Model -foods_collection = db["food_rating"] +# Food Model +foods_collection = db["food"] # Users Model users_collection = db["users"] +# Tasks Model +tasks_collection = db["tasks"] -# NOTE: This is temporary and will be replaced with a background task -from webscraper.food_locations import FoodLocations -# Fetch dining halls -fo = FoodLocations() -# Get dining halls as a list of json objects -dining_halls: list[dict] = [dh.to_dict() for dh in fo.get_locations()] -# Add dining halls to db -from .db_functions.dining_halls import remove_add_dining_halls_to_db +# # NOTE: This is temporary and will be replaced with a background task +# from webscraper.food_locations import FoodLocations -remove_add_dining_halls_to_db(dining_halls) +# # Fetch dining halls +# fo = FoodLocations() +# # Get dining halls as a list of json objects +# dining_halls: list[dict] = [dh.to_dict() for dh in fo.get_locations()] +# # Add dining halls to db +# from .db_functions.dining_halls import remove_add_dining_halls_to_db + +# remove_add_dining_halls_to_db(dining_halls) diff --git a/backend/myapi/urls.py b/backend/myapi/urls.py index 49ca2df..0e34851 100644 --- a/backend/myapi/urls.py +++ b/backend/myapi/urls.py @@ -3,5 +3,5 @@ urlpatterns = [ path("hello-world/", views.hello_world, name="hello_world"), - path("dining-halls/", views.get_dining_halls, name="dining_halls"), + path("locations/", views.get_locations, name="locations"), ] diff --git a/backend/myapi/views.py b/backend/myapi/views.py index cf63508..ae87d95 100644 --- a/backend/myapi/views.py +++ b/backend/myapi/views.py @@ -1,26 +1,57 @@ +from django.conf.locale import fr from rest_framework.response import Response from rest_framework.decorators import api_view -from .db_functions.dining_halls import get_all_dining_halls_from_db +from .db_functions.locations import get_all_locations_from_db, remove_add_locations_to_db +from .db_functions.tasks import set_task_last_update, get_task_last_update +from webscraper.food_locations import FoodLocations +from django.utils import timezone +from datetime import datetime + # Create your views here. @api_view(["GET"]) def hello_world(request): return Response({"message": "Hello, world!"}) -# Get the list of dining halls +# Get the list of locations at UCSC and their information @api_view(["GET"]) -def get_dining_halls(request): - # Get all dining halls from the db - dining_halls: list[dict] = get_all_dining_halls_from_db() +def get_locations(request): + # Get the last update time of the locations + last_update: datetime | None = get_task_last_update(task_name="locations") + + # get the current time and make it naive + time_now: datetime = timezone.now() + time_now = time_now.replace(tzinfo=None) + + print("Last time : ", last_update) + print("Current time: ", time_now) + + # check if not updated in the last hour + if last_update is None or (time_now - last_update).seconds > 3600: + print("Locations need to be updated...") + # fetch the locations from the web scraper and add them to the db + fo = FoodLocations() + locations: list[dict] = [dh.to_dict() for dh in fo.get_locations()] + # add the locations to the db + remove_add_locations_to_db(locations) + + # update the last update time + set_task_last_update(task_name="locations") + + else: + print("Locations are up to date. Getting from DB...") + # Get all locations from the db + locations: list[dict] = get_all_locations_from_db() # remove the _id field from each dining hall - for dh in dining_halls: - dh.pop("_id") + for dh in locations: + if "_id" in dh: + dh.pop("_id") # Convert the list of dining halls to json - json_data = {"locations": dining_halls} + json_data = {"locations": locations} return Response(json_data) From 1fd8b1476750b6703242da4ffac4bfd6bbbfae25 Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:06:30 -0700 Subject: [PATCH 13/19] Update js-ts-formatting-check.yml --- .github/workflows/js-ts-formatting-check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/js-ts-formatting-check.yml b/.github/workflows/js-ts-formatting-check.yml index ecf9ffe..0a90acb 100644 --- a/.github/workflows/js-ts-formatting-check.yml +++ b/.github/workflows/js-ts-formatting-check.yml @@ -2,7 +2,6 @@ name: Prettier Check on: push: {} - pull_request: {} jobs: prettier: From b4fa71e9b5dbe06372b3eb2b906c94d1bcb35496 Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:06:40 -0700 Subject: [PATCH 14/19] Update python-formatting-check.yml --- .github/workflows/python-formatting-check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-formatting-check.yml b/.github/workflows/python-formatting-check.yml index 7fbac4f..1bf8813 100644 --- a/.github/workflows/python-formatting-check.yml +++ b/.github/workflows/python-formatting-check.yml @@ -2,7 +2,6 @@ name: Black Formatting Check on: push: {} - pull_request: {} jobs: black: From e6ac84aa4f1d8af830a9ca5a70ce7b1101675569 Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:10:18 -0700 Subject: [PATCH 15/19] added black formatter --- flake.nix | 3 +-- requirements.txt | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index aad784f..9284054 100644 --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,6 @@ inherit system; }; my-python = pkgs.python3.withPackages (ps: with ps; [ - black pip ]); in @@ -53,4 +52,4 @@ }; }); }; -} \ No newline at end of file +} diff --git a/requirements.txt b/requirements.txt index 8eb5f7c..ccc4368 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,6 @@ requests>=2.31.0 # Database Dependencies pymongo[srv]>=3.12 dnspython + +# Python formatting +black>=24.4.2 From 408afbf766c63b1143a9d0e86a4fac5dbe0ff47b Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:11:02 -0700 Subject: [PATCH 16/19] formatted code using black --- backend/backend/asgi.py | 2 +- backend/backend/wsgi.py | 2 +- backend/myapi/apps.py | 4 +- backend/myapi/db_functions/tasks.py | 14 +++---- backend/myapi/db_functions/users.py | 60 ++++++++++++++++------------ backend/myapi/views.py | 6 ++- backend/utils.py | 25 +++++++----- backend/webscraper/category.py | 3 +- backend/webscraper/food.py | 18 ++++----- backend/webscraper/food_locations.py | 2 +- 10 files changed, 74 insertions(+), 62 deletions(-) diff --git a/backend/backend/asgi.py b/backend/backend/asgi.py index cb37c7d..0e40a3e 100644 --- a/backend/backend/asgi.py +++ b/backend/backend/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") application = get_asgi_application() diff --git a/backend/backend/wsgi.py b/backend/backend/wsgi.py index 83e5322..fe4a826 100644 --- a/backend/backend/wsgi.py +++ b/backend/backend/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") application = get_wsgi_application() diff --git a/backend/myapi/apps.py b/backend/myapi/apps.py index d3c4e44..9f3ff75 100644 --- a/backend/myapi/apps.py +++ b/backend/myapi/apps.py @@ -2,5 +2,5 @@ class MyapiConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'myapi' + default_auto_field = "django.db.models.BigAutoField" + name = "myapi" diff --git a/backend/myapi/db_functions/tasks.py b/backend/myapi/db_functions/tasks.py index 9ad7efc..022a610 100644 --- a/backend/myapi/db_functions/tasks.py +++ b/backend/myapi/db_functions/tasks.py @@ -10,6 +10,7 @@ last_update: str """ + def set_task_last_update(task_name: str) -> None: """ Set the time that the task were last update the time should be based upon the database time @@ -26,25 +27,22 @@ def set_task_last_update(task_name: str) -> None: # check if the task exists if task is None: # create a new task - task = { - "name": task_name, - "last_update": time_str - } + task = {"name": task_name, "last_update": time_str} # insert the task into the tasks collection tasks_collection.insert_one(task) else: # update the task tasks_collection.update_one( - {"name": task_name}, - {"$set": {"last_update": time_str}} + {"name": task_name}, {"$set": {"last_update": time_str}} ) + def get_task_last_update(task_name: str) -> datetime | None: """ Get the last update time of the task from the database """ # find the task in the tasks collection - task: dict | None = tasks_collection.find_one({"name": task_name}) + task: dict | None = tasks_collection.find_one({"name": task_name}) # check if the location exists if task is None: @@ -57,5 +55,3 @@ def get_task_last_update(task_name: str) -> datetime | None: last_update_time = datetime.strptime(last_update, "%Y-%m-%d %H:%M:%S") return last_update_time - - diff --git a/backend/myapi/db_functions/users.py b/backend/myapi/db_functions/users.py index 83cc8a2..0c0e8b1 100644 --- a/backend/myapi/db_functions/users.py +++ b/backend/myapi/db_functions/users.py @@ -1,44 +1,52 @@ from ..models import users_collection from pymongo.errors import PyMongoError + # Add user data to db, search by email/username def add_user_data(user_data): - try: - # Insert new user's data or update existing user's data by overwritting fields in user_data - result = users_collection.update_one({"email": user_data["email"]}, {"$set": user_data}, upsert=True) - if result.upserted_id: - print("User data inserted with id:", result.upserted_id) - else: - print("User data updated") - except PyMongoError as e: - print("Error while adding user data:", e) + try: + # Insert new user's data or update existing user's data by overwritting fields in user_data + result = users_collection.update_one( + {"email": user_data["email"]}, {"$set": user_data}, upsert=True + ) + if result.upserted_id: + print("User data inserted with id:", result.upserted_id) + else: + print("User data updated") + except PyMongoError as e: + print("Error while adding user data:", e) + # Get all the user's data from db, search by email/username def get_user_data(email): - try: - user_data = users_collection.find_one({"email": email}) - return user_data - except PyMongoError as e: - print("Error while getting user data:", e) - return None - + try: + user_data = users_collection.find_one({"email": email}) + return user_data + except PyMongoError as e: + print("Error while getting user data:", e) + return None + + # Get ratings data from user def get_ratings_data(email): try: - user_data = users_collection.find_one({"email": email}, {"_id": 0, "ratings": 1}) + user_data = users_collection.find_one( + {"email": email}, {"_id": 0, "ratings": 1} + ) # Return the "ratings" field, or an empty dictionary if not found - return user_data.get("ratings", {}) + return user_data.get("ratings", {}) except PyMongoError as e: print("Error while getting ratings data:", e) return {} + # Remove a user's data def remove_user_data(email): - try: - result = users_collection.delete_one({"email": email}) - if result.deleted_count == 1: - print("User data removed successfully") - else: - print("User not found") - except PyMongoError as e: - print("Error while removing user data:", e) \ No newline at end of file + try: + result = users_collection.delete_one({"email": email}) + if result.deleted_count == 1: + print("User data removed successfully") + else: + print("User not found") + except PyMongoError as e: + print("Error while removing user data:", e) diff --git a/backend/myapi/views.py b/backend/myapi/views.py index ae87d95..9575c9a 100644 --- a/backend/myapi/views.py +++ b/backend/myapi/views.py @@ -2,7 +2,10 @@ from rest_framework.response import Response from rest_framework.decorators import api_view -from .db_functions.locations import get_all_locations_from_db, remove_add_locations_to_db +from .db_functions.locations import ( + get_all_locations_from_db, + remove_add_locations_to_db, +) from .db_functions.tasks import set_task_last_update, get_task_last_update from webscraper.food_locations import FoodLocations @@ -10,6 +13,7 @@ from django.utils import timezone from datetime import datetime + # Create your views here. @api_view(["GET"]) def hello_world(request): diff --git a/backend/utils.py b/backend/utils.py index d26ecf4..da319c7 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -1,8 +1,13 @@ from pymongo import MongoClient -from private.private_settings import MONGODB_USERNAME, MONGODB_PASSWORD, MONGODB_CLUSTER, IS_DEV +from private.private_settings import ( + MONGODB_USERNAME, + MONGODB_PASSWORD, + MONGODB_CLUSTER, + IS_DEV, +) -def get_db_handle(db_name: str, password:str, username:str, cluster:str): +def get_db_handle(db_name: str, password: str, username: str, cluster: str): client = MongoClient(f"mongodb+srv://{username}:{password}@{cluster}") db_handle = client[db_name] @@ -15,15 +20,18 @@ def get_local_db_handle(db_name: str, host: str = "localhost", port: int = 27017 return db_handle, client - if __name__ == "__main__": - if IS_DEV and False: # Remove the False to test locally but you will have to have a local mongodb server running + if ( + IS_DEV and False + ): # Remove the False to test locally but you will have to have a local mongodb server running db_handle, client = get_local_db_handle("test") else: - db_handle, client = get_db_handle("test", MONGODB_PASSWORD, MONGODB_USERNAME, MONGODB_CLUSTER) + db_handle, client = get_db_handle( + "test", MONGODB_PASSWORD, MONGODB_USERNAME, MONGODB_CLUSTER + ) try: - client.admin.command('ping') + client.admin.command("ping") print("Pinged your deployment. You successfully connected to MongoDB!") except Exception as e: print(e) @@ -41,8 +49,8 @@ def get_local_db_handle(db_name: str, host: str = "localhost", port: int = 27017 "menu": { "breakfast": ["eggs", "bacon", "cereal"], "lunch": ["pizza", "salad", "sandwiches"], - "dinner": ["pasta", "soup", "chicken"] - } + "dinner": ["pasta", "soup", "chicken"], + }, } # insert the dining hall into the collection @@ -53,4 +61,3 @@ def get_local_db_handle(db_name: str, host: str = "localhost", port: int = 27017 result = test_collection.find_one(query) print("Found the following dining hall:") print(result) - diff --git a/backend/webscraper/category.py b/backend/webscraper/category.py index 5b5521e..e4ae750 100644 --- a/backend/webscraper/category.py +++ b/backend/webscraper/category.py @@ -15,7 +15,7 @@ def __str__(self) -> str: result += (" " * 2) + str(food) + "\n" return result - def to_dict(self) -> dict: + def to_dict(self) -> dict: return {"name": self.name, "foods": [food.to_dict() for food in self.foods]} @@ -65,7 +65,6 @@ def __process_data(self, html: Tag) -> list[SubCategory]: # add the food html data food_html_data.append(next_sibling) - return sub_categories diff --git a/backend/webscraper/food.py b/backend/webscraper/food.py index 9c73416..c46c69a 100644 --- a/backend/webscraper/food.py +++ b/backend/webscraper/food.py @@ -1,31 +1,29 @@ from bs4.element import Tag + class Food: def __init__(self, html: Tag) -> None: self.name = "Error: Food name not found" self.allergies = [] self.__process_data(html) - - def __process_data(self, html: Tag) -> None: - + # find the food name name = html.find("div", class_="shortmenurecipes") if name: self.name = name.get_text(strip=True) - for tags in html.find_all('img'): - temp = tags['src'].replace("LegendImages/", "") + for tags in html.find_all("img"): + temp = tags["src"].replace("LegendImages/", "") temp = temp.replace(".gif", "") self.allergies.append(temp) - def __str__(self) -> str: - if len(self.allergies) ==0: - return f'{self.name} --> No allergies' + if len(self.allergies) == 0: + return f"{self.name} --> No allergies" else: - s= ", ".join( [ str(element) for element in self.allergies ] ) - return f'{self.name} --> {s}' + s = ", ".join([str(element) for element in self.allergies]) + return f"{self.name} --> {s}" def to_dict(self) -> dict: # foodObj = {self.name: self.allergies} diff --git a/backend/webscraper/food_locations.py b/backend/webscraper/food_locations.py index 2108de0..53fa999 100644 --- a/backend/webscraper/food_locations.py +++ b/backend/webscraper/food_locations.py @@ -7,7 +7,7 @@ class FoodLocations: - main_url = "https://nutrition.sa.ucsc.edu/" + main_url = "https://nutrition.sa.ucsc.edu/" def __init__(self) -> None: self.locations: list[DiningHall] = self.__retrieve_data() From 873004db63ab4fd1db5aacf1b8baed3990ae95f4 Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:12:44 -0700 Subject: [PATCH 17/19] add prettier formatter --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 4f46718..4836101 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "jwt-decode": "^4.0.0", "next": "14.2.3", "npm": "^10.6.0", + "prettier": "^3.2.5", "react": "^18", "react-dom": "^18", "react-router-dom": "^6.23.0" From f6f6f79aefa160351e02fb58a520d69be1e4e060 Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:19:22 -0700 Subject: [PATCH 18/19] update github actions --- .github/workflows/js-ts-formatting-check.yml | 12 ++++-------- .github/workflows/python-formatting-check.yml | 6 +++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/js-ts-formatting-check.yml b/.github/workflows/js-ts-formatting-check.yml index 0a90acb..7fee76b 100644 --- a/.github/workflows/js-ts-formatting-check.yml +++ b/.github/workflows/js-ts-formatting-check.yml @@ -8,15 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@4 - - - name: Use latest Node.js - uses: actions/setup-node@v4 - with: - node-version: 'node' # Use 'node' for latest stable version + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 - name: Install dependencies - run: npm ci # Or yarn install if you use Yarn + run: bun install - name: Run Prettier check - run: npx prettier --check '**/*.{js,ts}' + run: bunx prettier --check '**/*.{js,ts,jsx,tsx}' diff --git a/.github/workflows/python-formatting-check.yml b/.github/workflows/python-formatting-check.yml index 1bf8813..f6b6c9a 100644 --- a/.github/workflows/python-formatting-check.yml +++ b/.github/workflows/python-formatting-check.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: psf/black@stable # Official Black action + - uses: psf/black@stable # Official Black action with: - options: "--check --verbose" # Check for formatting issues only - src: '.' # Path to your Python source code (optional) + options: "--check --verbose" # Check for formatting issues only + src: "." # Path to your Python source code (optional) From e0e01c78a9036063b16317f4dbaad6514e3dba4a Mon Sep 17 00:00:00 2001 From: Ian Holloway Date: Tue, 7 May 2024 14:19:36 -0700 Subject: [PATCH 19/19] formate all code with prettier --- README.md | 16 ++++----- app/[dh_choice]/accordian.css | 59 +++++++++++++++--------------- app/[dh_choice]/page.tsx | 43 +++++++++++++--------- app/global_search/page.tsx | 56 ++++++++++++++++++----------- app/page.tsx | 55 ++++++++++++++++++---------- app/search/page.tsx | 40 ++++++++++++++------- components/navbar.tsx | 67 +++++++++++++++++++---------------- 7 files changed, 198 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index c4e69f7..0f4e3bb 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next ## Running Code -* Make sure to have a private folder in the root directory that contains all secrets. (for now you can get this folder from our shared google drive folder) [link](https://drive.google.com/drive/folders/1oz7vGXPAI2S8vzYIpQ2ATTfjgqFHDD_p?usp=sharing) -* Make sure that you have all the python dependencies installed in the requirements.txt -* Make sure to install JS dependencies with `bun i` or `npm i` -* you then will have to have two terminals to run backend and frontend -* the backend is run with: `python backend/manage.py runserver` -* the frontend is run one of the commands below in the Getting Started -* Also, if you have nix installed with flakes you can skip most of the environment setup steps and use `nix develop` to setup your dev environment +- Make sure to have a private folder in the root directory that contains all secrets. (for now you can get this folder from our shared google drive folder) [link](https://drive.google.com/drive/folders/1oz7vGXPAI2S8vzYIpQ2ATTfjgqFHDD_p?usp=sharing) +- Make sure that you have all the python dependencies installed in the requirements.txt +- Make sure to install JS dependencies with `bun i` or `npm i` +- you then will have to have two terminals to run backend and frontend +- the backend is run with: `python backend/manage.py runserver` +- the frontend is run one of the commands below in the Getting Started +- Also, if you have nix installed with flakes you can skip most of the environment setup steps and use `nix develop` to setup your dev environment ## Getting Started @@ -24,8 +24,6 @@ pnpm dev bun dev ``` - - Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. diff --git a/app/[dh_choice]/accordian.css b/app/[dh_choice]/accordian.css index eb23a21..696ea83 100644 --- a/app/[dh_choice]/accordian.css +++ b/app/[dh_choice]/accordian.css @@ -1,33 +1,32 @@ - .accordion { - background-color: #ffffff; - border: transparent; - } - .accordion-item { - border: 1px solid #1321e1; - margin-bottom: 10px; - } - .accordion-button { - background-color: transparent; /* Transparent background */ - color: #003c6c; /* Category font color */ - padding: 10px; - font-size: 16px; - border-bottom: 1px solid #ffffff; - } - .accordion-collapse { - padding: 10px; - } + background-color: #ffffff; + border: transparent; +} +.accordion-item { + border: 1px solid #1321e1; + margin-bottom: 10px; +} +.accordion-button { + background-color: transparent; /* Transparent background */ + color: #003c6c; /* Category font color */ + padding: 10px; + font-size: 16px; + border-bottom: 1px solid #ffffff; +} +.accordion-collapse { + padding: 10px; +} - .sub-category { - background-color: transparent; - padding: 10px; - border-radius: 8px; - border-bottom: 1px solid #fdc700; - } +.sub-category { + background-color: transparent; + padding: 10px; + border-radius: 8px; + border-bottom: 1px solid #fdc700; +} - .food-item { - background-color: #9c9c9c; - padding: 8px; - margin-bottom: 4px; - border-radius: 4px; - } \ No newline at end of file +.food-item { + background-color: #9c9c9c; + padding: 8px; + margin-bottom: 4px; + border-radius: 4px; +} diff --git a/app/[dh_choice]/page.tsx b/app/[dh_choice]/page.tsx index 5ee7c2d..e5a65b0 100644 --- a/app/[dh_choice]/page.tsx +++ b/app/[dh_choice]/page.tsx @@ -1,13 +1,12 @@ "use client"; import React, { useState, useEffect, useRef } from "react"; import axios from "axios"; -import './accordian.css'; +import "./accordian.css"; import Link from "next/link"; - interface Food { name: string; - allergies: Array + allergies: Array; } interface subCategory { @@ -48,9 +47,7 @@ function Accordion({ category, index, isOpen }) { onClick={() => setIsOpenState(!isOpenState)} > {category.name} - - {isOpenState ? "▲" : "▼"} - + {isOpenState ? "▲" : "▼"} {isOpenState && (
    @@ -59,7 +56,9 @@ function Accordion({ category, index, isOpen }) {

    {sub_category.name}

      {sub_category.foods.map((food, k) => ( -
    • {food.name}
    • +
    • + {food.name} +
    • ))}
    @@ -72,7 +71,7 @@ function Accordion({ category, index, isOpen }) { export default function Page({ searchParams }) { const [categories, set_categories] = useState([]); - const [searchInput, setSearchInput] = useState(''); + const [searchInput, setSearchInput] = useState(""); const [filteredFoods, setFilteredFoods] = useState([]); const [expandedCategory, setExpandedCategory] = useState(null); const [noFoodsFound, setNoFoodsFound] = useState(false); @@ -96,7 +95,9 @@ export default function Page({ searchParams }) { return; } const timeOfDay = getTimeOfDay(); - const timeIndex = a_dh.categories.findIndex(category => category.name.toLowerCase() === timeOfDay); + const timeIndex = a_dh.categories.findIndex( + (category) => category.name.toLowerCase() === timeOfDay, + ); if (timeIndex !== -1) { setExpandedCategory(timeIndex); } @@ -105,18 +106,21 @@ export default function Page({ searchParams }) { console.log(error); }); }, []); - console.log(categories) - + console.log(categories); useEffect(() => { const timeOfDay = getTimeOfDay(); - const timeIndex = categories.findIndex(category => category.name.toLowerCase() === timeOfDay); + const timeIndex = categories.findIndex( + (category) => category.name.toLowerCase() === timeOfDay, + ); if (timeIndex !== -1) { setExpandedCategory(timeIndex); } }, [categories]); - const handleSearchInputChange = (event: React.ChangeEvent) => { + const handleSearchInputChange = ( + event: React.ChangeEvent, + ) => { setSearchInput(event.target.value); }; @@ -125,7 +129,7 @@ export default function Page({ searchParams }) { const searchResultPageUrl = `/search?diningHall=${encodeURIComponent(searchParams.name)}`; // Navigate to the search result page window.location.href = searchResultPageUrl; - localStorage.setItem('diningHall', dhChoice); + localStorage.setItem("diningHall", dhChoice); } function getTimeOfDay(): string { @@ -145,7 +149,10 @@ export default function Page({ searchParams }) { useEffect(() => { const handleOutsideClick = (event: MouseEvent) => { - if (searchRef.current && !searchRef.current.contains(event.target as Node)) { + if ( + searchRef.current && + !searchRef.current.contains(event.target as Node) + ) { setSearchActive(false); } }; @@ -170,7 +177,11 @@ export default function Page({ searchParams }) { {/* Categories */} {categories.map((category, i) => (
    - +
    ))}
    diff --git a/app/global_search/page.tsx b/app/global_search/page.tsx index 2fb7a97..01563b2 100644 --- a/app/global_search/page.tsx +++ b/app/global_search/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; +import React, { useState, useEffect } from "react"; +import axios from "axios"; interface Food { name: string; @@ -23,14 +23,16 @@ interface DiningHall { const BarebonesComponent = () => { const [dhs, setDhs] = useState([]); - const [searchInput, setSearchInput] = useState(''); - const [filteredFoods, setFilteredFoods] = useState<{ food: Food; dhName: string; categoryName: string }[]>([]); + const [searchInput, setSearchInput] = useState(""); + const [filteredFoods, setFilteredFoods] = useState< + { food: Food; dhName: string; categoryName: string }[] + >([]); const [showSearchResults, setShowSearchResults] = useState(false); const [noFoodsFound, setNoFoodsFound] = useState(false); useEffect(() => { axios - .get('http://localhost:8000/myapi/locations/') + .get("http://localhost:8000/myapi/locations/") .then((response) => { const dhsData: DiningHall[] = response.data.locations; setDhs(dhsData); @@ -40,7 +42,9 @@ const BarebonesComponent = () => { }); }, []); - const handleSearchInputChange = (event: React.ChangeEvent) => { + const handleSearchInputChange = ( + event: React.ChangeEvent, + ) => { setSearchInput(event.target.value); }; @@ -50,13 +54,19 @@ const BarebonesComponent = () => { dh.categories.forEach((category) => { category.sub_categories.forEach((subCategory) => { subCategory.foods.forEach((food) => { - allFoods.push({ food, dhName: dh.name, categoryName: category.name }); + allFoods.push({ + food, + dhName: dh.name, + categoryName: category.name, + }); }); }); }); }); - const filtered = allFoods.filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())); + const filtered = allFoods.filter(({ food }) => + food.name.toLowerCase().includes(searchInput.toLowerCase()), + ); setNoFoodsFound(filtered.length === 0); setFilteredFoods(filtered); @@ -64,19 +74,23 @@ const BarebonesComponent = () => { }; return ( -
    - {/* Title */} -

    Welcome to Hungry Slugs!

    - {/* Search bar */} -
    {/* Adjust margin as needed */} - - -
    +
    + {/* Title */} +

    Welcome to Hungry Slugs!

    + {/* Search bar */} +
    + {" "} + {/* Adjust margin as needed */} + + +
    {/* Display search results if button clicked */} {showSearchResults && (
    diff --git a/app/page.tsx b/app/page.tsx index 420c703..4d154a6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,12 +2,16 @@ import { useState, useEffect } from "react"; import axios from "axios"; import Link from "next/link"; -import { GoogleOAuthProvider, GoogleLogin, googleLogout } from "@react-oauth/google"; +import { + GoogleOAuthProvider, + GoogleLogin, + googleLogout, +} from "@react-oauth/google"; import { jwtDecode } from "jwt-decode"; interface Food { name: string; - extra_data: Array + extra_data: Array; } interface subCategory { @@ -29,7 +33,6 @@ interface User { picture: string; } - function ButtonLink(props: any) { return (
    @@ -49,8 +52,10 @@ function ButtonLink(props: any) { function Home() { const [dhs, setDhs] = useState([]); - const [searchInput, setSearchInput] = useState(''); - const [filteredFoods, setFilteredFoods] = useState<{ food: Food; dhName: string; categoryName: string }[]>([]); + const [searchInput, setSearchInput] = useState(""); + const [filteredFoods, setFilteredFoods] = useState< + { food: Food; dhName: string; categoryName: string }[] + >([]); const [showSearchResults, setShowSearchResults] = useState(false); const [noFoodsFound, setNoFoodsFound] = useState(false); @@ -66,7 +71,9 @@ function Home() { }); }, []); - const handleSearchInputChange = (event: React.ChangeEvent) => { + const handleSearchInputChange = ( + event: React.ChangeEvent, + ) => { setSearchInput(event.target.value); }; @@ -76,15 +83,20 @@ function Home() { dh.categories.forEach((category) => { category.sub_categories.forEach((subCategory) => { subCategory.foods.forEach((food) => { - allFoods.push({ food, dhName: dh.name, categoryName: category.name }); + allFoods.push({ + food, + dhName: dh.name, + categoryName: category.name, + }); }); }); }); }); - const filtered = allFoods - .filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())) - // .filter(({ food }, index, self) => self.findIndex(({ food }) => food.name === food.name) === index); + const filtered = allFoods.filter(({ food }) => + food.name.toLowerCase().includes(searchInput.toLowerCase()), + ); + // .filter(({ food }, index, self) => self.findIndex(({ food }) => food.name === food.name) === index); setNoFoodsFound(filtered.length === 0); setFilteredFoods(filtered); @@ -120,7 +132,7 @@ function Home() {
    )} - {noFoodsFound && ( + {noFoodsFound && (

    No foods found at this dining hall.

    @@ -141,27 +153,27 @@ function Home() { ); } - - export default function Page() { const [user, setUser] = useState(null); useEffect(() => { - console.log("Page component loaded and GoogleOAuthProvider should be active"); + console.log( + "Page component loaded and GoogleOAuthProvider should be active", + ); }, []); const handleLogout = () => { googleLogout(); setUser(null); // Clear user state on logout - console.log('Logout Successful'); + console.log("Logout Successful"); }; const handleLoginSuccess = (credentialResponse: any) => { - console.log('Login Successful', credentialResponse); + console.log("Login Successful", credentialResponse); const decoded: User = jwtDecode(credentialResponse.credential); setUser({ name: decoded.name, - picture: decoded.picture + picture: decoded.picture, }); }; @@ -171,7 +183,7 @@ export default function Page() { { - console.log('Login Failed'); + console.log("Login Failed"); }} /> {user && ( @@ -180,7 +192,12 @@ export default function Page() {

    {user.name}

    )} - + ); } diff --git a/app/search/page.tsx b/app/search/page.tsx index cc85bcd..5ef4800 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; +import React, { useState, useEffect } from "react"; +import axios from "axios"; interface Food { name: string; @@ -23,16 +23,18 @@ interface DiningHall { const BarebonesComponent = () => { const [dhs, setDhs] = useState([]); - const [searchInput, setSearchInput] = useState(''); - const [filteredFoods, setFilteredFoods] = useState<{ food: Food; dhName: string; categoryName: string }[]>([]); + const [searchInput, setSearchInput] = useState(""); + const [filteredFoods, setFilteredFoods] = useState< + { food: Food; dhName: string; categoryName: string }[] + >([]); const [showSearchResults, setShowSearchResults] = useState(false); const [noFoodsFound, setNoFoodsFound] = useState(false); - const diningHall = localStorage.getItem('diningHall'); + const diningHall = localStorage.getItem("diningHall"); useEffect(() => { axios - .get('http://localhost:8000/myapi/locations/') + .get("http://localhost:8000/myapi/locations/") .then((response) => { const dhsData: DiningHall[] = response.data.locations; setDhs(dhsData); @@ -42,7 +44,9 @@ const BarebonesComponent = () => { }); }, []); - const handleSearchInputChange = (event: React.ChangeEvent) => { + const handleSearchInputChange = ( + event: React.ChangeEvent, + ) => { setSearchInput(event.target.value); }; @@ -54,12 +58,18 @@ const BarebonesComponent = () => { currentDiningHall.categories.forEach((category) => { category.sub_categories.forEach((subCategory) => { subCategory.foods.forEach((food) => { - allFoods.push({ food, dhName: currentDiningHall.name, categoryName: category.name }); + allFoods.push({ + food, + dhName: currentDiningHall.name, + categoryName: category.name, + }); }); }); }); - const filtered = allFoods.filter(({ food }) => food.name.toLowerCase().includes(searchInput.toLowerCase())); + const filtered = allFoods.filter(({ food }) => + food.name.toLowerCase().includes(searchInput.toLowerCase()), + ); setNoFoodsFound(filtered.length === 0); setFilteredFoods(filtered); @@ -67,12 +77,18 @@ const BarebonesComponent = () => { }; return ( -
    +
    {/* Dining Hall Name */} -

    {diningHall} Search

    +

    + {diningHall} Search +

    {/* Search bar */} -
    {/* Adjust margin as needed */} +
    + {" "} + {/* Adjust margin as needed */} +
    + + + Hungry Slugs + + - - - - + +
  • + + Search + +
  • +
  • + + Account + + {/* pr-X dicates how far off right we want. */} +
  • +
  • + + {/* pr-X dicates how far off right we want. */} +
  • + +
    +
    +
    + ); -} \ No newline at end of file +}