costco-grocery-list/frontend/src/App.tsx
2025-11-15 00:45:19 -08:00

149 lines
3.5 KiB
TypeScript

import { useState, useEffect } from "react";
import SuggestionList from "./components/SuggestionList";
import GroceryItem from "./components/GroceryItem";
import type { GroceryItemType } from "./types";
export default function App() {
const [items, setItems] = useState<GroceryItemType[]>([]);
const [suggestions, setSuggestions] = useState<string[]>([]);
const [itemInput, setItemInput] = useState<string>("");
const [quantity, setQuantity] = useState<number>(1);
// Load grocery list
const loadList = async () => {
const res = await fetch("/api/list");
const data: GroceryItemType[] = await res.json();
setItems(data);
};
useEffect(() => {
loadList();
}, []);
// Suggestion logic
const handleSuggest = async (query: string) => {
setItemInput(query);
if (!query.trim()) return setSuggestions([]);
const res = await fetch("/api/suggest?query=" + encodeURIComponent(query));
const data: string[] = await res.json();
setSuggestions(data);
};
// Add Item
const addItem = async () => {
if (!itemInput.trim() || !quantity) return;
const existing = items.find(
(i) => i.item_name.toLowerCase() === itemInput.toLowerCase()
);
if (existing) {
if (!confirm("Item already exists. Add more?")) return;
await fetch("/api/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
item_name: itemInput,
quantity: existing.quantity + quantity,
}),
});
} else {
await fetch("/api/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ item_name: itemInput, quantity }),
});
}
setItemInput("");
setQuantity(1);
setSuggestions([]);
loadList();
};
// Mark bought
const markBought = async (id: number) => {
if (!confirm("Mark this item as bought?")) return;
await fetch("/api/mark-bought", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id }),
});
loadList();
};
return (
<div
className="container"
style={{
maxWidth: "480px",
margin: "auto",
background: "white",
padding: "1em",
borderRadius: "8px",
boxShadow: "0 0 10px rgba(0,0,0,0.1)",
}}
>
<h1 style={{ textAlign: "center", fontSize: "1.5em" }}>
Costco Grocery List
</h1>
<input
type="text"
value={itemInput}
placeholder="Item name"
onChange={(e) => handleSuggest(e.target.value)}
style={inputStyle}
autoComplete="off"
/>
<SuggestionList
suggestions={suggestions}
onSelect={(val: string) => {
setItemInput(val);
setSuggestions([]);
}}
/>
<input
type="number"
value={quantity}
min={1}
onChange={(e) => setQuantity(Number(e.target.value))}
placeholder="Quantity"
style={inputStyle}
/>
<button onClick={addItem} style={buttonStyle}>
Add Item
</button>
<ul style={{ listStyle: "none", padding: 0 }}>
{items.map((item) => (
<GroceryItem key={item.id} item={item} onClick={markBought} />
))}
</ul>
</div>
);
}
const inputStyle: React.CSSProperties = {
fontSize: "1em",
margin: "0.3em 0",
padding: "0.5em",
width: "100%",
boxSizing: "border-box",
};
const buttonStyle = {
...inputStyle,
cursor: "pointer",
};