v0.6.1 — Pantry: categories, price tracking, and autocomplete
This commit is contained in:
@@ -15,6 +15,10 @@ const updateSchema = z.object({
|
||||
bestBefore: z.string().nullable().optional(),
|
||||
source: z.nativeEnum(ItemSource).nullable().optional(),
|
||||
plantId: z.string().nullable().optional(),
|
||||
pantryCategory: z.string().nullable().optional(),
|
||||
pantrySubcategory: z.string().nullable().optional(),
|
||||
pricePaid: z.number().nullable().optional(),
|
||||
priceUnit: z.string().nullable().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
@@ -35,11 +39,16 @@ export async function PATCH(req: Request, { params }: { params: { id: string } }
|
||||
...(data.bestBefore !== undefined && { bestBefore: data.bestBefore ? new Date(data.bestBefore) : null }),
|
||||
...(data.source !== undefined && { source: data.source ?? null }),
|
||||
...(data.plantId !== undefined && { plantId: data.plantId ?? null }),
|
||||
...(data.pantryCategory !== undefined && { pantryCategory: data.pantryCategory ?? null }),
|
||||
...(data.pantrySubcategory !== undefined && { pantrySubcategory: data.pantrySubcategory ?? null }),
|
||||
...(data.pricePaid !== undefined && { pricePaid: data.pricePaid ?? null }),
|
||||
...(data.priceUnit !== undefined && { priceUnit: data.priceUnit ?? null }),
|
||||
...(data.notes !== undefined && { notes: data.notes ?? null }),
|
||||
},
|
||||
select: {
|
||||
id: true, name: true, description: true, quantity: true, unit: true,
|
||||
storedAt: true, bestBefore: true, source: true, plantId: true, notes: true,
|
||||
pantryCategory: true, pantrySubcategory: true, pricePaid: true, priceUnit: true,
|
||||
location: { select: { id: true, name: true } },
|
||||
plant: { select: { id: true, commonName: true, variety: true } },
|
||||
},
|
||||
|
||||
64
src/app/api/pantry/catalog/route.ts
Normal file
64
src/app/api/pantry/catalog/route.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "@/lib/db";
|
||||
import { requireAuth } from "@/lib/auth";
|
||||
|
||||
// Returns distinct pantry item names + their most-recent settings for autocomplete.
|
||||
export async function GET(req: Request) {
|
||||
await requireAuth();
|
||||
const { searchParams } = new URL(req.url);
|
||||
const q = searchParams.get("q")?.trim().toLowerCase() ?? "";
|
||||
|
||||
// Grab all consumable items (active + inactive so we remember old entries).
|
||||
const items = await db.item.findMany({
|
||||
where: {
|
||||
kind: "CONSUMABLE",
|
||||
...(q ? { name: { contains: q, mode: "insensitive" } } : {}),
|
||||
},
|
||||
orderBy: { updatedAt: "desc" },
|
||||
select: {
|
||||
name: true, description: true, unit: true, source: true,
|
||||
pantryCategory: true, pantrySubcategory: true,
|
||||
priceUnit: true, pricePaid: true,
|
||||
locationId: true, plantId: true,
|
||||
location: { select: { id: true, name: true } },
|
||||
},
|
||||
take: 200,
|
||||
});
|
||||
|
||||
// Deduplicate by name (case-insensitive), keep the most-recent record per name.
|
||||
const seen = new Map<string, typeof items[number]>();
|
||||
for (const item of items) {
|
||||
const key = item.name.toLowerCase();
|
||||
if (!seen.has(key)) seen.set(key, item);
|
||||
}
|
||||
|
||||
// Also return distinct categories/subcategories for autocomplete dropdowns.
|
||||
const allCats = await db.item.findMany({
|
||||
where: { kind: "CONSUMABLE", pantryCategory: { not: null } },
|
||||
select: { pantryCategory: true, pantrySubcategory: true },
|
||||
distinct: ["pantryCategory", "pantrySubcategory"],
|
||||
});
|
||||
|
||||
const categories = Array.from(new Set(allCats.map(c => c.pantryCategory).filter(Boolean))) as string[];
|
||||
const subcategories = allCats
|
||||
.filter(c => c.pantrySubcategory)
|
||||
.map(c => ({ category: c.pantryCategory!, subcategory: c.pantrySubcategory! }));
|
||||
|
||||
return NextResponse.json({
|
||||
suggestions: Array.from(seen.values()).map(i => ({
|
||||
name: i.name,
|
||||
description: i.description,
|
||||
unit: i.unit,
|
||||
source: i.source,
|
||||
pantryCategory: i.pantryCategory,
|
||||
pantrySubcategory: i.pantrySubcategory,
|
||||
priceUnit: i.priceUnit,
|
||||
pricePaid: i.pricePaid != null ? Number(i.pricePaid) : null,
|
||||
locationId: i.locationId,
|
||||
locationName: i.location?.name ?? null,
|
||||
plantId: i.plantId,
|
||||
})),
|
||||
categories,
|
||||
subcategories,
|
||||
});
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { requireAuth } from "@/lib/auth";
|
||||
const PANTRY_SELECT = {
|
||||
id: true, name: true, description: true, quantity: true, unit: true,
|
||||
storedAt: true, bestBefore: true, source: true, plantId: true, notes: true,
|
||||
pantryCategory: true, pantrySubcategory: true, pricePaid: true, priceUnit: true,
|
||||
location: { select: { id: true, name: true } },
|
||||
plant: { select: { id: true, commonName: true, variety: true } },
|
||||
} as const;
|
||||
@@ -18,6 +19,10 @@ function serialize(i: any) {
|
||||
packageSize: i.description,
|
||||
storedAt: i.storedAt, bestBefore: i.bestBefore,
|
||||
source: i.source, notes: i.notes,
|
||||
pantryCategory: i.pantryCategory,
|
||||
pantrySubcategory: i.pantrySubcategory,
|
||||
pricePaid: i.pricePaid != null ? Number(i.pricePaid) : null,
|
||||
priceUnit: i.priceUnit,
|
||||
locationId: i.location?.id ?? null,
|
||||
locationName: i.location?.name ?? null,
|
||||
plantId: i.plant?.id ?? null,
|
||||
@@ -46,6 +51,10 @@ const createSchema = z.object({
|
||||
bestBefore: z.string().nullable().optional(),
|
||||
source: z.nativeEnum(ItemSource).nullable().optional(),
|
||||
plantId: z.string().nullable().optional(),
|
||||
pantryCategory: z.string().nullable().optional(),
|
||||
pantrySubcategory: z.string().nullable().optional(),
|
||||
pricePaid: z.number().nullable().optional(),
|
||||
priceUnit: z.string().nullable().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
@@ -66,6 +75,10 @@ export async function POST(req: Request) {
|
||||
bestBefore: data.bestBefore ? new Date(data.bestBefore) : null,
|
||||
source: data.source ?? null,
|
||||
plantId: data.plantId ?? null,
|
||||
pantryCategory: data.pantryCategory ?? null,
|
||||
pantrySubcategory: data.pantrySubcategory ?? null,
|
||||
pricePaid: data.pricePaid ?? null,
|
||||
priceUnit: data.priceUnit ?? null,
|
||||
notes: data.notes ?? null,
|
||||
},
|
||||
select: PANTRY_SELECT,
|
||||
|
||||
Reference in New Issue
Block a user