// The Seed Tray: a small, deliberately-curated set of plants a gardener keeps at // hand while planting a bed, so repeat placement is a single tap instead of a // trip through the full catalog. It's a per-garden convenience, persisted in // localStorage (same rationale as the PlantPicker's "recent plants"): a nicety, // not authoritative state, so a quota/availability failure is swallowed. // // Only plant ids are stored; they resolve against the live catalog on read, so a // renamed plant stays current and a deleted one silently drops out of the tray. import { useCallback, useEffect, useMemo, useState } from 'react' import { usePlants, type Plant } from './plants' const KEY = (gardenId: number) => `pansy:seed-tray:${gardenId}` const TRAY_MAX = 24 // a working set, not a catalog; caps pathological growth function loadIds(gardenId: number): number[] { try { const v: unknown = JSON.parse(localStorage.getItem(KEY(gardenId)) ?? '[]') const ids = Array.isArray(v) ? v.filter((n): n is number => typeof n === 'number') : [] return ids.slice(-TRAY_MAX) // honor the cap even if storage was hand-edited } catch { return [] } } function saveIds(gardenId: number, ids: number[]) { try { localStorage.setItem(KEY(gardenId), JSON.stringify(ids)) } catch { // The tray is a convenience; ignore quota/availability failures. } } export interface SeedTrayState { /** Tray plants in insertion order, resolved against the live catalog. */ trayPlants: Plant[] /** Add a plant to the end of the tray (no-op if already present; when the tray * is full the oldest entry is evicted). */ add: (plant: Plant) => void /** Remove a plant from the tray. */ remove: (id: number) => void } /** Per-garden Seed Tray backed by localStorage and the plant catalog. */ export function useSeedTray(gardenId: number): SeedTrayState { const plants = usePlants() const [ids, setIds] = useState(() => loadIds(gardenId)) // Re-read when switching gardens (the route param changes without remounting). useEffect(() => { setIds(loadIds(gardenId)) }, [gardenId]) const byId = useMemo(() => new Map((plants.data ?? []).map((p) => [p.id, p])), [plants.data]) // Resolve ids → plants, dropping any no longer in the catalog; preserve order. const trayPlants = useMemo( () => ids.map((id) => byId.get(id)).filter((p): p is Plant => !!p), [ids, byId], ) const add = useCallback( (plant: Plant) => setIds((prev) => { if (prev.includes(plant.id)) return prev const next = [...prev, plant.id].slice(-TRAY_MAX) saveIds(gardenId, next) return next }), [gardenId], ) const remove = useCallback( (id: number) => setIds((prev) => { const next = prev.filter((x) => x !== id) saveIds(gardenId, next) return next }), [gardenId], ) return { trayPlants, add, remove } }