Skip to content

Commit df2e4d0

Browse files
committed
grocery list almost done
1 parent 0812d56 commit df2e4d0

4 files changed

Lines changed: 193 additions & 45 deletions

File tree

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,64 @@
1-
import { GroceryListIngredient } from '../shared/types'
2-
import { useState } from 'react';
1+
import { GroceryListIngredient } from '../shared/types';
2+
import { useState, useEffect } from 'react';
33

44
type GroceryListIngredientCardProps = {
5-
item: GroceryListIngredient;
5+
item: GroceryListIngredient;
6+
startingQuantity?: number;
7+
onQuantityChange?: (newQty: number) => void;
68
};
79

8-
export default function GroceryListIngredientCard({ item }: GroceryListIngredientCardProps) {
10+
export default function GroceryListIngredientCard({
11+
item,
12+
startingQuantity,
13+
onQuantityChange
14+
}: GroceryListIngredientCardProps) {
915
const { ingredient, quantity } = item;
10-
const [currentQuantity, setCurrentQuanity] = useState<string>(quantity);
16+
const [currentQuantity, setCurrentQuantity] = useState<number>(startingQuantity ?? Number(quantity));
17+
18+
useEffect(() => {
19+
setCurrentQuantity(startingQuantity ?? Number(quantity));
20+
}, [startingQuantity, quantity]);
21+
22+
const updateQuantity = (newQty: number) => {
23+
setCurrentQuantity(newQty);
24+
onQuantityChange?.(newQty);
25+
};
1126

1227
return (
13-
<div className="w-full max-w-[20rem] justify-between items-center rounded-[12px] border-[1.5rem] border-white bg-white p-4">
14-
<div className="flex flex-row items-center gap-6">
15-
<div className="flex flex-col items-center">
16-
<img
17-
className="h-32 w-auto max-w-full object-cover"
18-
src={ingredient.image_id}
19-
alt={ingredient.name}
20-
/>
21-
<p className="text-center text-lg font-semibold">{ingredient.name}</p>
22-
</div>
23-
<div className="flex flex-row max-w-[6rem] h-8 items-center justify-center space-x-2">
24-
<button
25-
className="w-0 h-0 border-l-[0.75rem] border-l-transparent border-r-[0.75rem] border-r-transparent border-b-[1rem] border-b-[#D9D9D9]"
26-
onClick={() => setCurrentQuanity(String(Number(currentQuantity) + 1))}
27-
></button>
28-
29-
<input
30-
type="text"
31-
className="w-10 h-8 border-[1px] border-[#D9D9D9] text-center font-bold text-lg"
32-
value={currentQuantity}
33-
onChange={e => setCurrentQuanity(e.target.value)}
34-
/>
35-
36-
<button
37-
className="w-0 h-0 border-l-[0.75rem] border-l-transparent border-r-[0.75rem] border-r-transparent border-t-[1rem] border-t-[#D9D9D9]"
38-
onClick={() => {
39-
if (Number(currentQuantity) > 0) {
40-
setCurrentQuanity(String(Number(currentQuantity) - 1));
41-
}}}
42-
></button>
43-
</div>
28+
<div className="flex w-full max-w-xs flex-col md:flex-row items-center justify-between gap-4 rounded-lg border border-gray-200 bg-white p-4">
29+
{/* Image & Name */}
30+
<div className="flex flex-col items-center md:items-start gap-2">
31+
<img
32+
className="h-24 w-24 object-cover rounded-md"
33+
src={ingredient.image_id}
34+
alt={ingredient.name}
35+
/>
36+
<p className="text-center md:text-left text-black font-semibold">{ingredient.name}</p>
37+
</div>
38+
39+
{/* Quantity Controls (Vertical) */}
40+
<div className="flex flex-col items-center gap-1">
41+
<button
42+
className="w-8 h-8 flex items-center justify-center rounded bg-gray-200 text-lg font-bold"
43+
onClick={() => updateQuantity(currentQuantity + 1)}
44+
>
45+
+
46+
</button>
47+
48+
<input
49+
type="number"
50+
className="w-8 text-center border border-gray-300 text-black rounded px-1"
51+
value={currentQuantity}
52+
onChange={e => updateQuantity(Math.max(0, Number(e.target.value)))}
53+
/>
54+
55+
<button
56+
className="w-8 h-8 flex items-center justify-center rounded bg-gray-200 text-lg font-bold"
57+
onClick={() => updateQuantity(Math.max(0, currentQuantity - 1))}
58+
>
59+
-
60+
</button>
4461
</div>
4562
</div>
46-
)
63+
);
4764
}
Lines changed: 128 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,132 @@
1+
import { useState, useEffect } from "react";
12
import CookbookBar from "../../cookbook_components/CookbookBar";
3+
import ImageCard from "../../shared/components/ImageCard";
4+
import GroceryListIngredientCard from "../../cookbook_components/GroceryListIngredientCard";
5+
import { recipes } from "../../shared/data/dummyRecipes";
6+
import { RecipeIngredient } from "../../shared/types";
27

38
export default function GroceryList() {
4-
return (
5-
<div className="p-10">
6-
<CookbookBar showMyRecipes={false}/>
7-
<div className="mt-5 flex flex-row">
8-
</div>
9+
// Track recipe quantities
10+
const [quantities, setQuantities] = useState<Record<string, number>>(
11+
recipes.reduce((acc, recipe) => {
12+
acc[recipe.id] = 0;
13+
return acc;
14+
}, {} as Record<string, number>)
15+
);
16+
17+
// Track ingredient totals separately
18+
const [ingredientTotals, setIngredientTotals] = useState<Record<string, RecipeIngredient>>({});
19+
console.log(ingredientTotals)
20+
21+
const increaseQuantity = (id: string) => setQuantities(prev => ({ ...prev, [id]: prev[id] + 1 }));
22+
const decreaseQuantity = (id: string) =>
23+
setQuantities(prev => ({ ...prev, [id]: prev[id] > 0 ? prev[id] - 1 : 0 }));
24+
25+
// Recompute ingredient totals whenever recipe quantities change
26+
useEffect(() => {
27+
const totals: Record<string, RecipeIngredient> = {};
28+
console.log(totals)
29+
30+
recipes.forEach(recipe => {
31+
const recipeQty = quantities[recipe.id] || 0;
32+
if (recipeQty > 0) {
33+
recipe.ingredients.forEach(ri => {
34+
const totalQty = ri.storeQuantity * recipeQty;
35+
if (!totals[ri.ingredient.id]) {
36+
totals[ri.ingredient.id] = {
37+
ingredient: ri.ingredient,
38+
quantity: totalQty.toString(),
39+
storeQuantity: totalQty,
40+
};
41+
} else {
42+
const prev = totals[ri.ingredient.id];
43+
const newStoreQty = prev.storeQuantity + totalQty;
44+
totals[ri.ingredient.id] = {
45+
ingredient: ri.ingredient,
46+
quantity: newStoreQty.toString(),
47+
storeQuantity: newStoreQty,
48+
};
49+
}
50+
});
51+
}
52+
});
53+
54+
setIngredientTotals(totals);
55+
}, [quantities]);
56+
57+
const uniqueIngredients = Object.values(ingredientTotals);
58+
59+
const handleIngredientChange = (id: string, newQty: number) => {
60+
setIngredientTotals(prev => {
61+
const updated = { ...prev };
62+
if (updated[id]) {
63+
updated[id] = {
64+
...updated[id],
65+
storeQuantity: newQty,
66+
quantity: newQty.toString(),
67+
};
68+
}
69+
return updated;
70+
});
71+
};
72+
73+
return (
74+
<div className="p-10">
75+
<CookbookBar showMyRecipes={false} />
76+
77+
<div className="mt-5 flex flex-row gap-5">
78+
{/* Recipes */}
79+
<div className="bg-white flex-[2] h-[70vh] rounded-xl overflow-y-auto p-4">
80+
<div className="flex flex-col items-center gap-6">
81+
{recipes.map(recipe => (
82+
<div key={recipe.id} className="w-full max-w-[90%] relative">
83+
<ImageCard
84+
src={recipe.image_id || ""}
85+
alt={recipe.title}
86+
caption={recipe.title}
87+
height={150}
88+
className="w-full border-black border-2 relative"
89+
/>
90+
<div className="absolute top-10 right-2 flex flex-col items-center gap-1">
91+
<button
92+
onClick={() => increaseQuantity(recipe.id)}
93+
className="px-2 py-1 bg-[#EB5904] text-white font-bold rounded hover:bg-orange-600 transition"
94+
>
95+
+
96+
</button>
97+
<span className="bg-white px-2 py-1 rounded font-semibold text-black text-sm">
98+
{quantities[recipe.id]}
99+
</span>
100+
<button
101+
onClick={() => decreaseQuantity(recipe.id)}
102+
className="px-2 py-1 bg-[#EB5904] text-white font-bold rounded hover:bg-orange-600 transition"
103+
>
104+
-
105+
</button>
106+
</div>
107+
</div>
108+
))}
109+
</div>
110+
</div>
111+
112+
{/* Grocery List */}
113+
<div className="bg-white flex-[1] h-[70vh] rounded-xl p-4 overflow-y-auto">
114+
<div className="flex flex-col gap-4">
115+
{uniqueIngredients.map(item => (
116+
<GroceryListIngredientCard
117+
key={item.ingredient.id}
118+
item={{
119+
ingredient: item.ingredient,
120+
quantity: Math.ceil(item.storeQuantity).toString(),
121+
purchased_status: false,
122+
}}
123+
startingQuantity={Math.ceil(item.storeQuantity)}
124+
onQuantityChange={newQty => handleIngredientChange(item.ingredient.id, newQty)}
125+
/>
126+
))}
127+
</div>
9128
</div>
10-
);
11-
};
129+
</div>
130+
</div>
131+
);
132+
}

rise-dc-app/src/shared/data/dummyIngredients.ts

Whitespace-only changes.

rise-dc-app/src/shared/data/dummyRecipes.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Recipe } from "../types";
1+
import { Ingredient, Recipe, RecipeIngredient } from "../types";
22

33
const placeholderImage =
44
"https://www.cranfield-colours.co.uk/wp-content/uploads/2022/01/cranfield-traditional-etching-ink-mid-black-400x221.jpg";
@@ -1151,3 +1151,13 @@ export function toggleFavorite(recipeId: string) {
11511151
export function getRecipe(recipeId: string) {
11521152
return recipes.find((r) => r.id === recipeId);
11531153
}
1154+
1155+
export const allUniqueIngredients: Ingredient[] = Array.from(
1156+
recipes
1157+
.flatMap((recipe) => recipe.ingredients.map((ri) => ri.ingredient))
1158+
.reduce((map, ingredient) => {
1159+
map.set(ingredient.id, ingredient);
1160+
return map;
1161+
}, new Map<string, Ingredient>())
1162+
.values()
1163+
);

0 commit comments

Comments
 (0)