149 lines
3.5 KiB
TypeScript
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",
|
|
};
|