v0.6.2 — Pantry: searchable category/subcategory comboboxes

This commit is contained in:
Bonna Moon
2026-06-29 17:42:54 -05:00
parent 62a0089ec2
commit 608a192877
5 changed files with 226 additions and 33 deletions

View File

@@ -5,6 +5,8 @@ import { Plus, Pencil, Sparkles, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Combobox } from "@/components/ui/combobox";
import { CATEGORY_OPTIONS, getSubcategoryOptions } from "@/lib/pantry/categories";
import type { PantryItem, LocationOption, PlantOption } from "./pantry-client";
const SOURCES = [
@@ -19,15 +21,6 @@ const SOURCES = [
const COMMON_UNITS = ["lbs", "oz", "g", "kg", "bags", "jars", "quart jars", "pint jars", "gallon bags", "quart bags", "containers", "cans", "bottles", "bunches", "loaves", "portions", "each"];
const PRICE_UNITS = ["package", "lb", "oz", "kg", "each", "dozen", "bunch"];
const DEFAULT_CATEGORIES = ["Meat", "Produce", "Dairy & Eggs", "Baked Goods", "Soups & Broths", "Sauces & Condiments", "Grains & Legumes", "Herbs & Spices", "Prepared Meals", "Fruit", "Seafood", "Other"];
const DEFAULT_SUBCATEGORIES: Record<string, string[]> = {
"Meat": ["Beef", "Poultry", "Pork", "Lamb", "Game", "Sausage", "Other"],
"Produce": ["Berries", "Vegetables", "Leafy Greens", "Root Vegetables", "Corn", "Peppers", "Tomatoes", "Other"],
"Dairy & Eggs": ["Butter", "Cheese", "Milk", "Cream", "Eggs", "Other"],
"Baked Goods": ["Bread", "Muffins", "Cookies", "Cake", "Pie", "Crackers", "Other"],
"Fruit": ["Berries", "Stone Fruit", "Apples & Pears", "Citrus", "Tropical", "Other"],
"Seafood": ["Fish", "Shrimp", "Shellfish", "Other"],
};
type Suggestion = {
name: string;
@@ -107,7 +100,7 @@ export function PantryItemDialog({ item, locations, plants, onSuccess }: Props)
setForm(f => ({ ...f, [key]: val }));
}
// Load catalog on open (for category/subcategory autocomplete)
// Load catalog on open (for name autocomplete suggestions)
useEffect(() => {
if (!open) return;
fetch("/api/pantry/catalog")
@@ -155,13 +148,7 @@ export function PantryItemDialog({ item, locations, plants, onSuccess }: Props)
set("name", s.name);
}
// Subcategory options: merge defaults + previously used
const allCategories = Array.from(new Set([...DEFAULT_CATEGORIES, ...(catalog?.categories ?? [])])).sort();
const subcatDefaults = DEFAULT_SUBCATEGORIES[form.pantryCategory] ?? [];
const subcatFromCatalog = (catalog?.subcategories ?? [])
.filter(s => s.category === form.pantryCategory)
.map(s => s.subcategory);
const allSubcategories = Array.from(new Set([...subcatDefaults, ...subcatFromCatalog])).sort();
const subcategoryOptions = getSubcategoryOptions(form.pantryCategory);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
@@ -274,30 +261,23 @@ export function PantryItemDialog({ item, locations, plants, onSuccess }: Props)
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label>Category</Label>
<input
className={inputCls}
list="cat-list"
<Combobox
options={CATEGORY_OPTIONS}
value={form.pantryCategory}
onChange={e => { set("pantryCategory", e.target.value); set("pantrySubcategory", ""); }}
placeholder="e.g. Meat"
onChange={v => { set("pantryCategory", v); set("pantrySubcategory", ""); }}
placeholder="Select category…"
/>
<datalist id="cat-list">
{allCategories.map(c => <option key={c} value={c} />)}
</datalist>
</div>
<div className="space-y-1">
<Label>Subcategory</Label>
<input
className={inputCls}
list="subcat-list"
<Combobox
options={subcategoryOptions}
value={form.pantrySubcategory}
onChange={e => set("pantrySubcategory", e.target.value)}
placeholder="e.g. Beef"
onChange={v => set("pantrySubcategory", v)}
placeholder="Select subcategory…"
disabled={!form.pantryCategory}
emptyText="No subcategories"
/>
<datalist id="subcat-list">
{allSubcategories.map(c => <option key={c} value={c} />)}
</datalist>
</div>
</div>