import React, { useEffect, useState, useMemo } from "react";
import Card from "/components/Card/Card.js";
import CardBody from "/components/Card/CardBody.js";
import CardFooter from "/components/Card/CardFooter.js";
import CardHeader from "/components/Card/CardHeader.js";
import GridContainer from "/components/Grid/GridContainer.js";
import GridItem from "/components/Grid/GridItem.js";
import CustomInput from "/components/CustomInput/CustomInput.js";
import Link from "next/link";
import CardMedia from "@material-ui/core/CardMedia";
import CardActionArea from "@material-ui/core/CardActionArea";
import InputAdornment from "@material-ui/core/InputAdornment";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch";
// nodejs library that concatenates classes
import classNames from "classnames";
// @material-ui/core components
import { makeStyles } from "@material-ui/core/styles";
import { debounce } from "lodash"; // Import debounce function
import styles from "/styles/jss/nextjs-material-kit/pages/components.js";
import {
getDatabase,
ref,
get,
query,
orderByChild,
equalTo,
} from "firebase/database";
/** The line `export const database = getDatabase();` is exporting a constant named `database` that is
assigned the result of calling the `getDatabase()` function. In this context, `getDatabase()` is
likely a function provided by Firebase SDK that initializes and returns a reference to the Firebase
Realtime Database. */
export const database = getDatabase();
const useStyles = makeStyles(styles);
/** These lines of code are setting up state variables using the `useState` hook in React. Here's what
each state variable is responsible for: */
/**
* State to check state of radiobutton for each food item
*/
export const [checkedStates, setCheckedStates] = useState([]);
/**
* State to store queried food
*/
export const [searchedFood, setSearchedFood] = useState("");
/**
* State to store food data
*/
export const [foods, setFood] = useState([]);
/**
* State to store debounced search term
*/
export const [debouncedSearch, setDebouncedSearch] = useState("");
/**
* The `ManualContentPill` function in JavaScript fetches and filters food data based on a search
* query, debounces the search input, and displays food information with toggleable serving options.
* @returns The `ManualContentPill` component is returning a grid layout containing a search input
* field for food search, cards displaying food information such as calories, fats, carbs, and protein,
* along with a toggle switch to show the nutritional values per serving or per 100g. Each card also
* has a "More Info" button that links to a FoodInfo page for detailed information about the specific
* food item
*/
export function ManualContentPill() {
const [checkedStates, setCheckedStates] = useState([]);
const [searchedFood, setSearchedFood] = useState("");
const [foods, setFood] = useState([]);
const [debouncedSearch, setDebouncedSearch] = useState("");
useEffect(() => {
const fetchFoods = async () => {
if (debouncedSearch.trim() === "") {
setFood([]);
return;
}
const foodRef = ref(database, "food/");
try {
const snapshot = await get(foodRef);
if (snapshot.exists()) {
const foodArray = Object.entries(snapshot.val()).map(
([idd, data]) => ({
idd,
...data,
})
);
const filteredFoods = foodArray.filter((food) =>
food.idd.toLowerCase().includes(debouncedSearch.toLowerCase())
);
setFood(filteredFoods);
setCheckedStates(Array(foodArray.length).fill(false));
} else {
console.log("No data available");
}
} catch (error) {
console.error("Error retrieving data:", error);
}
};
fetchFoods();
}, [debouncedSearch]);
return (
<GridContainer justify="left" spacing={4}>
<GridItem xs={12}>
<CustomInput
labelText="Search Food"
id="float"
formControlProps={{
fullWidth: true,
}}
inputProps={{
type: "text",
onChange: handleFoodSearch,
endAdornment: <InputAdornment position="end"></InputAdornment>,
}}
/>
</GridItem>
{cachedFoods.map((food, index) => (
<GridItem xs={12} sm={6} md={4} lg={4} key={food.idd}>
<Card style={{ backgroundColor: "#fff9c4" }}>
<CardMedia
align="left"
component="img"
alt="Image cannot be loaded"
width="100"
height="200"
image={
food.category !== "Others"
? `/img/foods/${food.idd}.jpg`
: "/img/fudpic.jpg"
}
title="Picture of a food"
/>
<CardBody>
<h4 className={food.idd} style={{ fontWeight: "bold" }}>
{food.idd}
</h4>
<p>
Calories:{" "}
{checkedStates[index] ? food.scalories : food.calories}
</p>
<p>Fats: {checkedStates[index] ? food.sfats : food.fats}</p>
<p>Carbs: {checkedStates[index] ? food.scarbs : food.carbs}</p>
<p>
Protein: {checkedStates[index] ? food.sprotein : food.protein}
</p>
</CardBody>
<CardFooter>
{/* More Info button linking to FoodInfo page */}
<Link href={`/foodinfo?id=${food.id}`}>
<a>More Info</a>
</Link>
</CardFooter>
<div
style={{
position: "absolute",
bottom: 0,
right: 0,
margin: "16px",
}}
>
<FormControlLabel
control={
<Switch
checked={checkedStates[index]}
onChange={() => handleToggleChange(index)}
value={`checked${index}`}
classes={{
switchBase: classes.switchBase,
checked: classes.switchChecked,
thumb: classes.switchIcon,
track: classes.switchBar,
}}
/>
}
id={{
label: food.idd,
}}
label={
checkedStates[index]
? "Showing per serving"
: "Showing per 100g"
}
/>
</div>
</Card>
</GridItem>
))}
</GridContainer>
);
}
/**
* The function fetches food data from a database, filters it based on a search query, and updates the
* state accordingly.
* @returns If the `debouncedSearch` is empty after trimming, the function will set the `food` state to
* an empty array and then return.
*/
export const fetchFoods = async () => {
if (debouncedSearch.trim() === "") {
setFood([]);
return;
}
const foodRef = ref(database, "food/");
try {
const snapshot = await get(foodRef);
if (snapshot.exists()) {
const foodArray = Object.entries(snapshot.val()).map(([idd, data]) => ({
idd,
...data,
}));
const filteredFoods = foodArray.filter((food) =>
food.idd.toLowerCase().includes(debouncedSearch.toLowerCase())
);
setFood(filteredFoods);
setCheckedStates(Array(foodArray.length).fill(false));
} else {
console.log("No data available");
}
} catch (error) {
console.error("Error retrieving data:", error);
}
};
/** The code snippet `export const debouncedSearchFunction = debounce((searchValue) => {
setDebouncedSearch(searchValue);
}, 500);` is defining a function named `debouncedSearchFunction` that utilizes the `debounce`
function from the lodash library. */
export const debouncedSearchFunction = debounce((searchValue) => {
setDebouncedSearch(searchValue);
}, 500);
/** The line `const cachedFoods = useMemo(() => foods, [foods]);` is utilizing the `useMemo` hook in
React to memoize the `foods` state variable. */
export const cachedFoods = useMemo(() => foods, [foods]);
/**
* The handleFoodSearch function updates the searchedFood state with the value entered in an input
* field.
* @param event - The `event` parameter in the `handleFoodSearch` function is an object that represents
* the event that occurred, such as a user input event. In this case, it is capturing the value entered
* by the user in an input field.
*/
export const handleFoodSearch = (event) => {
const enteredFood = event.target.value;
setSearchedFood(enteredFood);
};
/**
* The function `handleToggleChange` toggles the boolean value at a specific index in an array and
* updates the state with the new array.
* @param index - The `index` parameter in the `handleToggleChange` function represents the index of
* the item in the `checkedStates` array that needs to be toggled.
*/
export const handleToggleChange = (index) => {
const newCheckedStates = [...checkedStates];
newCheckedStates[index] = !newCheckedStates[index];
setCheckedStates(newCheckedStates);
};