v0.2.0 — Plant autocomplete + annual/perennial flower categories
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
-- Add annual flower and perennial flower to PlantCategory enum
|
||||||
|
ALTER TYPE "PlantCategory" ADD VALUE 'ANNUAL_FLOWER';
|
||||||
|
ALTER TYPE "PlantCategory" ADD VALUE 'PERENNIAL_FLOWER';
|
||||||
|
ALTER TYPE "PlantCategory" ADD VALUE 'ANNUAL_VEGETABLE';
|
||||||
@@ -61,10 +61,13 @@ enum PlantCategory {
|
|||||||
HERB // comfrey, yarrow, mint, chives
|
HERB // comfrey, yarrow, mint, chives
|
||||||
GROUNDCOVER // strawberries, clover, creeping thyme
|
GROUNDCOVER // strawberries, clover, creeping thyme
|
||||||
VINE // grapes, hops, kiwi
|
VINE // grapes, hops, kiwi
|
||||||
ANNUAL // tomatoes, squash, beans — seasonal
|
ANNUAL_VEGETABLE // tomatoes, squash, beans, peppers — seasonal veg
|
||||||
PERENNIAL // asparagus, rhubarb, perennial veg
|
ANNUAL // legacy, treated as annual vegetable
|
||||||
BULB // garlic, onions, tulips
|
ANNUAL_FLOWER // zinnias, marigolds, nasturtiums, cosmos
|
||||||
MUSHROOM // shiitake log, oyster bed, etc.
|
PERENNIAL // asparagus, rhubarb, perennial veg
|
||||||
|
PERENNIAL_FLOWER // coneflower, bee balm, black-eyed susan
|
||||||
|
BULB // garlic, onions, tulips, daffodils
|
||||||
|
MUSHROOM // shiitake log, oyster bed, etc.
|
||||||
OTHER
|
OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -17,8 +17,10 @@ import {
|
|||||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus, Leaf } from "lucide-react";
|
||||||
import { CATEGORY_LABELS, CATEGORY_ORDER } from "@/lib/garden/categories";
|
import { CATEGORY_LABELS, CATEGORY_ORDER } from "@/lib/garden/categories";
|
||||||
|
import { searchPlants, type PlantSuggestion } from "@/lib/garden/plant-db";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
commonName: z.string().min(1, "Name is required"),
|
commonName: z.string().min(1, "Name is required"),
|
||||||
@@ -34,6 +36,10 @@ type FormValues = z.infer<typeof schema>;
|
|||||||
|
|
||||||
export function AddPlantButton() {
|
export function AddPlantButton() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [suggestions, setSuggestions] = useState<PlantSuggestion[]>([]);
|
||||||
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||||
|
const [category, setCategory] = useState<PlantCategory>("CANOPY_TREE");
|
||||||
|
const nameRef = useRef<HTMLInputElement>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@@ -42,6 +48,23 @@ export function AddPlantButton() {
|
|||||||
defaultValues: { category: "CANOPY_TREE" },
|
defaultValues: { category: "CANOPY_TREE" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onNameChange(value: string) {
|
||||||
|
form.setValue("commonName", value);
|
||||||
|
const results = searchPlants(value);
|
||||||
|
setSuggestions(results);
|
||||||
|
setShowSuggestions(results.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySuggestion(suggestion: PlantSuggestion) {
|
||||||
|
form.setValue("commonName", suggestion.commonName);
|
||||||
|
if (suggestion.species) form.setValue("species", suggestion.species);
|
||||||
|
if (suggestion.notes && !form.getValues("notes")) form.setValue("notes", suggestion.notes);
|
||||||
|
form.setValue("category", suggestion.category);
|
||||||
|
setCategory(suggestion.category);
|
||||||
|
setSuggestions([]);
|
||||||
|
setShowSuggestions(false);
|
||||||
|
}
|
||||||
|
|
||||||
async function onSubmit(values: FormValues) {
|
async function onSubmit(values: FormValues) {
|
||||||
const res = await fetch("/api/plants", {
|
const res = await fetch("/api/plants", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -57,13 +80,21 @@ export function AddPlantButton() {
|
|||||||
toast({ title: "Plant added!", description: `${values.commonName} is now in your food forest.` });
|
toast({ title: "Plant added!", description: `${values.commonName} is now in your food forest.` });
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
form.reset();
|
form.reset();
|
||||||
|
setCategory("CANOPY_TREE");
|
||||||
|
setSuggestions([]);
|
||||||
router.push(`/garden/${plant.id}`);
|
router.push(`/garden/${plant.id}`);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleOpen() {
|
||||||
|
setOpen(true);
|
||||||
|
setSuggestions([]);
|
||||||
|
setShowSuggestions(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => setOpen(true)} size="sm">
|
<Button onClick={handleOpen} size="sm">
|
||||||
<Plus className="h-4 w-4 mr-1" />
|
<Plus className="h-4 w-4 mr-1" />
|
||||||
Add plant
|
Add plant
|
||||||
</Button>
|
</Button>
|
||||||
@@ -76,12 +107,48 @@ export function AddPlantButton() {
|
|||||||
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="col-span-2 space-y-1.5">
|
|
||||||
<Label htmlFor="commonName">Common name *</Label>
|
{/* Name with autocomplete */}
|
||||||
<Input id="commonName" placeholder="Apple, Comfrey, Raspberry…" {...form.register("commonName")} />
|
<div className="col-span-2 space-y-1.5 relative">
|
||||||
|
<Label htmlFor="commonName">What plant is it? *</Label>
|
||||||
|
<Input
|
||||||
|
id="commonName"
|
||||||
|
ref={nameRef}
|
||||||
|
placeholder="Start typing — apple, comfrey, zinnia…"
|
||||||
|
value={form.watch("commonName") ?? ""}
|
||||||
|
onChange={(e) => onNameChange(e.target.value)}
|
||||||
|
onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
|
||||||
|
onFocus={() => suggestions.length > 0 && setShowSuggestions(true)}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
{form.formState.errors.commonName && (
|
{form.formState.errors.commonName && (
|
||||||
<p className="text-xs text-destructive">{form.formState.errors.commonName.message}</p>
|
<p className="text-xs text-destructive">{form.formState.errors.commonName.message}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Suggestion dropdown */}
|
||||||
|
{showSuggestions && (
|
||||||
|
<div className="absolute top-full left-0 right-0 z-50 mt-1 bg-popover border rounded-md shadow-lg overflow-hidden">
|
||||||
|
{suggestions.map((s) => (
|
||||||
|
<button
|
||||||
|
key={`${s.commonName}-${s.category}`}
|
||||||
|
type="button"
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||||
|
onMouseDown={() => applySuggestion(s)}
|
||||||
|
>
|
||||||
|
<Leaf className="h-3.5 w-3.5 shrink-0 text-[hsl(var(--leaf))] opacity-70" />
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="text-sm font-medium">{s.commonName}</p>
|
||||||
|
{s.species && (
|
||||||
|
<p className="text-xs text-muted-foreground italic truncate">{s.species} · {CATEGORY_LABELS[s.category]}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<div className="px-3 py-2 border-t text-xs text-muted-foreground">
|
||||||
|
Don't see it? Just type the name and fill in the rest yourself.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -92,8 +159,12 @@ export function AddPlantButton() {
|
|||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Category *</Label>
|
<Label>Category *</Label>
|
||||||
<Select
|
<Select
|
||||||
defaultValue="CANOPY_TREE"
|
value={category}
|
||||||
onValueChange={(v) => form.setValue("category", v as PlantCategory)}
|
onValueChange={(v) => {
|
||||||
|
const c = v as PlantCategory;
|
||||||
|
form.setValue("category", c);
|
||||||
|
setCategory(c);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
@@ -107,7 +178,7 @@ export function AddPlantButton() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-2 space-y-1.5">
|
<div className="col-span-2 space-y-1.5">
|
||||||
<Label htmlFor="species">Scientific name (optional)</Label>
|
<Label htmlFor="species">Scientific name <span className="text-muted-foreground font-normal">(filled in automatically if known)</span></Label>
|
||||||
<Input id="species" placeholder="Malus domestica" className="italic" {...form.register("species")} />
|
<Input id="species" placeholder="Malus domestica" className="italic" {...form.register("species")} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -128,7 +199,7 @@ export function AddPlantButton() {
|
|||||||
|
|
||||||
<div className="col-span-2 space-y-1.5">
|
<div className="col-span-2 space-y-1.5">
|
||||||
<Label htmlFor="notes">Notes</Label>
|
<Label htmlFor="notes">Notes</Label>
|
||||||
<Textarea id="notes" placeholder="Any notes about this plant…" rows={3} {...form.register("notes")} />
|
<Textarea id="notes" placeholder="Anything worth remembering about this plant…" rows={3} {...form.register("notes")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ export type ChangelogEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
version: "0.2.0",
|
||||||
|
date: "2026-06-15",
|
||||||
|
changes: [
|
||||||
|
"Add plant: type a name and the app suggests the scientific name and category automatically from a built-in database of 150+ plants. You can accept, ignore, or change anything it fills in.",
|
||||||
|
"New plant categories: Annual flower (zinnias, marigolds, nasturtiums…), Perennial flower (coneflower, bee balm, black-eyed Susan…), and Annual vegetable (tomatoes, squash, beans…).",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: "0.1.1",
|
version: "0.1.1",
|
||||||
date: "2026-06-15",
|
date: "2026-06-15",
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import { PlantCategory, PlantLogType } from "@prisma/client";
|
import { PlantCategory, PlantLogType } from "@prisma/client";
|
||||||
|
|
||||||
export const CATEGORY_LABELS: Record<PlantCategory, string> = {
|
export const CATEGORY_LABELS: Record<PlantCategory, string> = {
|
||||||
CANOPY_TREE: "Canopy tree",
|
CANOPY_TREE: "Canopy tree",
|
||||||
UNDERSTORY_TREE: "Understory tree",
|
UNDERSTORY_TREE: "Understory tree",
|
||||||
LARGE_SHRUB: "Large shrub",
|
LARGE_SHRUB: "Large shrub",
|
||||||
SHRUB: "Shrub",
|
SHRUB: "Shrub",
|
||||||
HERB: "Herb",
|
HERB: "Herb",
|
||||||
GROUNDCOVER: "Groundcover",
|
GROUNDCOVER: "Groundcover",
|
||||||
VINE: "Vine",
|
VINE: "Vine",
|
||||||
ANNUAL: "Annual",
|
ANNUAL_VEGETABLE: "Annual vegetable",
|
||||||
PERENNIAL: "Perennial",
|
ANNUAL: "Annual vegetable",
|
||||||
BULB: "Bulb",
|
ANNUAL_FLOWER: "Annual flower",
|
||||||
MUSHROOM: "Mushroom",
|
PERENNIAL: "Perennial",
|
||||||
OTHER: "Other",
|
PERENNIAL_FLOWER: "Perennial flower",
|
||||||
|
BULB: "Bulb",
|
||||||
|
MUSHROOM: "Mushroom",
|
||||||
|
OTHER: "Other",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CATEGORY_ORDER: PlantCategory[] = [
|
export const CATEGORY_ORDER: PlantCategory[] = [
|
||||||
@@ -24,7 +27,9 @@ export const CATEGORY_ORDER: PlantCategory[] = [
|
|||||||
"GROUNDCOVER",
|
"GROUNDCOVER",
|
||||||
"VINE",
|
"VINE",
|
||||||
"PERENNIAL",
|
"PERENNIAL",
|
||||||
"ANNUAL",
|
"PERENNIAL_FLOWER",
|
||||||
|
"ANNUAL_VEGETABLE",
|
||||||
|
"ANNUAL_FLOWER",
|
||||||
"BULB",
|
"BULB",
|
||||||
"MUSHROOM",
|
"MUSHROOM",
|
||||||
"OTHER",
|
"OTHER",
|
||||||
@@ -32,18 +37,18 @@ export const CATEGORY_ORDER: PlantCategory[] = [
|
|||||||
|
|
||||||
export const LOG_TYPE_LABELS: Record<PlantLogType, string> = {
|
export const LOG_TYPE_LABELS: Record<PlantLogType, string> = {
|
||||||
OBSERVATION: "Observation",
|
OBSERVATION: "Observation",
|
||||||
CARE: "Care",
|
CARE: "Care",
|
||||||
HARVEST: "Harvest",
|
HARVEST: "Harvest",
|
||||||
TREATMENT: "Treatment",
|
TREATMENT: "Treatment",
|
||||||
TRANSPLANT: "Transplanted",
|
TRANSPLANT: "Transplanted",
|
||||||
DIED: "Died / removed",
|
DIED: "Died / removed",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LOG_TYPE_COLORS: Record<PlantLogType, string> = {
|
export const LOG_TYPE_COLORS: Record<PlantLogType, string> = {
|
||||||
OBSERVATION: "text-blue-600 dark:text-blue-400",
|
OBSERVATION: "text-blue-600 dark:text-blue-400",
|
||||||
CARE: "text-[hsl(var(--leaf))]",
|
CARE: "text-[hsl(var(--leaf))]",
|
||||||
HARVEST: "text-amber-600 dark:text-amber-400",
|
HARVEST: "text-amber-600 dark:text-amber-400",
|
||||||
TREATMENT: "text-orange-600 dark:text-orange-400",
|
TREATMENT: "text-orange-600 dark:text-orange-400",
|
||||||
TRANSPLANT: "text-purple-600 dark:text-purple-400",
|
TRANSPLANT: "text-purple-600 dark:text-purple-400",
|
||||||
DIED: "text-destructive",
|
DIED: "text-destructive",
|
||||||
};
|
};
|
||||||
|
|||||||
292
src/lib/garden/plant-db.ts
Normal file
292
src/lib/garden/plant-db.ts
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import { PlantCategory } from "@prisma/client";
|
||||||
|
|
||||||
|
export type PlantSuggestion = {
|
||||||
|
commonName: string;
|
||||||
|
species?: string;
|
||||||
|
category: PlantCategory;
|
||||||
|
notes?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Built-in plant database — searched when you type a plant name.
|
||||||
|
// Scientific names are the species-level name where possible.
|
||||||
|
// Variety is always left blank — the user fills that in themselves.
|
||||||
|
export const PLANT_DB: PlantSuggestion[] = [
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Canopy trees
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Apple", species: "Malus domestica", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Pear", species: "Pyrus communis", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Plum", species: "Prunus domestica", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Sweet cherry", species: "Prunus avium", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Walnut, black", species: "Juglans nigra", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Walnut, English", species: "Juglans regia", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Chestnut", species: "Castanea spp.", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Hickory", species: "Carya spp.", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Mulberry", species: "Morus spp.", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Persimmon", species: "Diospyros virginiana", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Pecan", species: "Carya illinoinensis", category: "CANOPY_TREE" },
|
||||||
|
{ commonName: "Quince", species: "Cydonia oblonga", category: "CANOPY_TREE" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Understory trees
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Sour cherry", species: "Prunus cerasus", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Peach", species: "Prunus persica", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Nectarine", species: "Prunus persica", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Apricot", species: "Prunus armeniaca", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Pawpaw", species: "Asimina triloba", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Serviceberry", species: "Amelanchier spp.", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Elderberry", species: "Sambucus nigra", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Crabapple", species: "Malus spp.", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Nanking cherry", species: "Prunus tomentosa", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Redbud", species: "Cercis canadensis", category: "UNDERSTORY_TREE" },
|
||||||
|
{ commonName: "Dogwood", species: "Cornus spp.", category: "UNDERSTORY_TREE" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Large shrubs
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Hazelnut", species: "Corylus americana", category: "LARGE_SHRUB" },
|
||||||
|
{ commonName: "Hazelnut, European", species: "Corylus avellana", category: "LARGE_SHRUB" },
|
||||||
|
{ commonName: "Arrowwood viburnum", species: "Viburnum dentatum", category: "LARGE_SHRUB" },
|
||||||
|
{ commonName: "Nannyberry", species: "Viburnum lentago", category: "LARGE_SHRUB" },
|
||||||
|
{ commonName: "Highbush cranberry", species: "Viburnum trilobum", category: "LARGE_SHRUB" },
|
||||||
|
{ commonName: "Lilac", species: "Syringa vulgaris", category: "LARGE_SHRUB" },
|
||||||
|
{ commonName: "Buttonbush", species: "Cephalanthus occidentalis", category: "LARGE_SHRUB" },
|
||||||
|
{ commonName: "Mock orange", species: "Philadelphus coronarius", category: "LARGE_SHRUB" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Shrubs / berries
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Raspberry", species: "Rubus idaeus", category: "SHRUB" },
|
||||||
|
{ commonName: "Blackberry", species: "Rubus allegheniensis", category: "SHRUB" },
|
||||||
|
{ commonName: "Blueberry", species: "Vaccinium corymbosum", category: "SHRUB" },
|
||||||
|
{ commonName: "Currant, black", species: "Ribes nigrum", category: "SHRUB" },
|
||||||
|
{ commonName: "Currant, red", species: "Ribes rubrum", category: "SHRUB" },
|
||||||
|
{ commonName: "Currant, white", species: "Ribes rubrum", category: "SHRUB" },
|
||||||
|
{ commonName: "Gooseberry", species: "Ribes uva-crispa", category: "SHRUB" },
|
||||||
|
{ commonName: "Aronia / Chokeberry", species: "Aronia melanocarpa", category: "SHRUB" },
|
||||||
|
{ commonName: "Rose", species: "Rosa spp.", category: "SHRUB" },
|
||||||
|
{ commonName: "Rugosa rose", species: "Rosa rugosa", category: "SHRUB" },
|
||||||
|
{ commonName: "Jostaberry", species: "Ribes × nidigrolaria", category: "SHRUB" },
|
||||||
|
{ commonName: "Goji berry", species: "Lycium barbarum", category: "SHRUB" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Herbs
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Comfrey", species: "Symphytum officinale", category: "HERB", notes: "Dynamic accumulator. Chop-and-drop around fruit trees." },
|
||||||
|
{ commonName: "Yarrow", species: "Achillea millefolium", category: "HERB" },
|
||||||
|
{ commonName: "Mint", species: "Mentha spp.", category: "HERB", notes: "Spreads aggressively — consider planting in containers." },
|
||||||
|
{ commonName: "Spearmint", species: "Mentha spicata", category: "HERB" },
|
||||||
|
{ commonName: "Peppermint", species: "Mentha × piperita", category: "HERB" },
|
||||||
|
{ commonName: "Chives", species: "Allium schoenoprasum", category: "HERB" },
|
||||||
|
{ commonName: "Garlic chives", species: "Allium tuberosum", category: "HERB" },
|
||||||
|
{ commonName: "Oregano", species: "Origanum vulgare", category: "HERB" },
|
||||||
|
{ commonName: "Thyme", species: "Thymus vulgaris", category: "HERB" },
|
||||||
|
{ commonName: "Sage", species: "Salvia officinalis", category: "HERB" },
|
||||||
|
{ commonName: "Rosemary", species: "Salvia rosmarinus", category: "HERB" },
|
||||||
|
{ commonName: "Lavender", species: "Lavandula angustifolia", category: "HERB" },
|
||||||
|
{ commonName: "Lemon balm", species: "Melissa officinalis", category: "HERB" },
|
||||||
|
{ commonName: "Fennel", species: "Foeniculum vulgare", category: "HERB" },
|
||||||
|
{ commonName: "Parsley", species: "Petroselinum crispum", category: "HERB" },
|
||||||
|
{ commonName: "Tarragon", species: "Artemisia dracunculus", category: "HERB" },
|
||||||
|
{ commonName: "Lovage", species: "Levisticum officinale", category: "HERB" },
|
||||||
|
{ commonName: "Hyssop", species: "Hyssopus officinalis", category: "HERB" },
|
||||||
|
{ commonName: "Catnip", species: "Nepeta cataria", category: "HERB" },
|
||||||
|
{ commonName: "Valerian", species: "Valeriana officinalis", category: "HERB" },
|
||||||
|
{ commonName: "St. John's wort", species: "Hypericum perforatum", category: "HERB" },
|
||||||
|
{ commonName: "Echinacea / Coneflower", species: "Echinacea purpurea", category: "HERB" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Groundcovers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Strawberry", species: "Fragaria × ananassa", category: "GROUNDCOVER" },
|
||||||
|
{ commonName: "Wild strawberry", species: "Fragaria vesca", category: "GROUNDCOVER" },
|
||||||
|
{ commonName: "Creeping thyme", species: "Thymus serpyllum", category: "GROUNDCOVER" },
|
||||||
|
{ commonName: "White clover", species: "Trifolium repens", category: "GROUNDCOVER" },
|
||||||
|
{ commonName: "Sweet woodruff", species: "Galium odoratum", category: "GROUNDCOVER" },
|
||||||
|
{ commonName: "Ajuga / Bugleweed", species: "Ajuga reptans", category: "GROUNDCOVER" },
|
||||||
|
{ commonName: "Creeping Jenny", species: "Lysimachia nummularia", category: "GROUNDCOVER" },
|
||||||
|
{ commonName: "Mint (groundcover)", species: "Mentha spp.", category: "GROUNDCOVER" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Vines
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Grape", species: "Vitis spp.", category: "VINE" },
|
||||||
|
{ commonName: "Hops", species: "Humulus lupulus", category: "VINE" },
|
||||||
|
{ commonName: "Hardy kiwi", species: "Actinidia arguta", category: "VINE" },
|
||||||
|
{ commonName: "Kiwi, fuzzy", species: "Actinidia deliciosa", category: "VINE" },
|
||||||
|
{ commonName: "Climbing rose", species: "Rosa spp.", category: "VINE" },
|
||||||
|
{ commonName: "Clematis", species: "Clematis spp.", category: "VINE" },
|
||||||
|
{ commonName: "Wisteria", species: "Wisteria spp.", category: "VINE" },
|
||||||
|
{ commonName: "Trumpet vine", species: "Campsis radicans", category: "VINE" },
|
||||||
|
{ commonName: "Passionflower", species: "Passiflora incarnata", category: "VINE" },
|
||||||
|
{ commonName: "Honeysuckle", species: "Lonicera spp.", category: "VINE" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Perennial vegetables
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Asparagus", species: "Asparagus officinalis", category: "PERENNIAL" },
|
||||||
|
{ commonName: "Rhubarb", species: "Rheum rhabarbarum", category: "PERENNIAL" },
|
||||||
|
{ commonName: "Artichoke", species: "Cynara cardunculus", category: "PERENNIAL" },
|
||||||
|
{ commonName: "Horseradish", species: "Armoracia rusticana", category: "PERENNIAL" },
|
||||||
|
{ commonName: "Good King Henry", species: "Blitum bonus-henricus", category: "PERENNIAL" },
|
||||||
|
{ commonName: "Sorrel", species: "Rumex acetosa", category: "PERENNIAL" },
|
||||||
|
{ commonName: "Turkish rocket", species: "Bunias orientalis", category: "PERENNIAL" },
|
||||||
|
{ commonName: "Nine star broccoli", species: "Brassica oleracea", category: "PERENNIAL" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Perennial flowers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Coneflower", species: "Echinacea purpurea", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Black-eyed Susan", species: "Rudbeckia hirta", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Bee balm", species: "Monarda didyma", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Wild bergamot", species: "Monarda fistulosa", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Peony", species: "Paeonia lactiflora", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Hosta", species: "Hosta spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Daylily", species: "Hemerocallis spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Iris", species: "Iris spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Salvia, perennial", species: "Salvia nemorosa", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Catmint", species: "Nepeta × faassenii", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Astilbe", species: "Astilbe spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Coreopsis", species: "Coreopsis spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Lupine", species: "Lupinus spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Delphinium", species: "Delphinium spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Hollyhock", species: "Alcea rosea", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Foxglove", species: "Digitalis purpurea", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Phlox", species: "Phlox paniculata", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Sedum / Stonecrop", species: "Sedum spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Russian sage", species: "Perovskia atriplicifolia", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Goldenrod", species: "Solidago spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Joe Pye weed", species: "Eutrochium purpureum", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Bleeding heart", species: "Lamprocapnos spectabilis", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Columbine", species: "Aquilegia spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Coral bells", species: "Heuchera spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Liatris / Blazing star", species: "Liatris spicata", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Milkweed", species: "Asclepias spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Butterfly weed", species: "Asclepias tuberosa", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Monkshood", species: "Aconitum spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Prairie dropseed", species: "Sporobolus heterolepis", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Coneflower, yellow", species: "Ratibida pinnata", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "False indigo", species: "Baptisia australis", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Shasta daisy", species: "Leucanthemum × superbum", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Spiderwort", species: "Tradescantia ohiensis", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Tickseed", species: "Coreopsis tinctoria", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Rose mallow", species: "Hibiscus moscheutos", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Obedient plant", species: "Physostegia virginiana", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Globe thistle", species: "Echinops spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Agastache", species: "Agastache spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Gaillardia / Blanket flower", species: "Gaillardia spp.", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Veronicastrum", species: "Veronicastrum virginicum", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Turtlehead", species: "Chelone glabra", category: "PERENNIAL_FLOWER" },
|
||||||
|
{ commonName: "Wild ginger", species: "Asarum canadense", category: "PERENNIAL_FLOWER" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Annual vegetables
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Tomato", species: "Solanum lycopersicum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Pepper, sweet", species: "Capsicum annuum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Pepper, hot", species: "Capsicum annuum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Eggplant", species: "Solanum melongena", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Zucchini", species: "Cucurbita pepo", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Summer squash", species: "Cucurbita pepo", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Winter squash", species: "Cucurbita maxima", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Pumpkin", species: "Cucurbita pepo", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Cucumber", species: "Cucumis sativus", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Watermelon", species: "Citrullus lanatus", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Cantaloupe / Melon", species: "Cucumis melo", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Bean, green / snap", species: "Phaseolus vulgaris", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Bean, pole", species: "Phaseolus vulgaris", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Bean, dried", species: "Phaseolus vulgaris", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Pea", species: "Pisum sativum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Pea, snow", species: "Pisum sativum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Lettuce", species: "Lactuca sativa", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Spinach", species: "Spinacia oleracea", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Kale", species: "Brassica oleracea", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Swiss chard", species: "Beta vulgaris", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Beet", species: "Beta vulgaris", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Carrot", species: "Daucus carota", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Radish", species: "Raphanus sativus", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Turnip", species: "Brassica rapa", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Sweet corn", species: "Zea mays", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Sweet potato", species: "Ipomoea batatas", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Potato", species: "Solanum tuberosum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Onion", species: "Allium cepa", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Leek", species: "Allium ampeloprasum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Cabbage", species: "Brassica oleracea", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Broccoli", species: "Brassica oleracea", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Cauliflower", species: "Brassica oleracea", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Brussels sprouts", species: "Brassica oleracea", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Kohlrabi", species: "Brassica oleracea", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Arugula", species: "Eruca vesicaria", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Cilantro / Coriander", species: "Coriandrum sativum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Dill", species: "Anethum graveolens", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Basil", species: "Ocimum basilicum", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Sunflower", species: "Helianthus annuus", category: "ANNUAL_VEGETABLE" },
|
||||||
|
{ commonName: "Okra", species: "Abelmoschus esculentus", category: "ANNUAL_VEGETABLE" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Annual flowers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Zinnia", species: "Zinnia elegans", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Marigold", species: "Tagetes spp.", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Nasturtium", species: "Tropaeolum majus", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Calendula", species: "Calendula officinalis", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Cosmos", species: "Cosmos bipinnatus", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Bachelor's button", species: "Centaurea cyanus", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Sweet pea", species: "Lathyrus odoratus", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Larkspur", species: "Consolida ajacis", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Snapdragon", species: "Antirrhinum majus", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Cleome / Spider flower", species: "Cleome hassleriana", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Borage", species: "Borago officinalis", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Morning glory", species: "Ipomoea purpurea", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Petunia", species: "Petunia × atkinsiana", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Pansy", species: "Viola × wittrockiana", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Statice", species: "Limonium sinuatum", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Ammi / False Queen Anne's lace", species: "Ammi majus", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Celosia", species: "Celosia argentea", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Nigella / Love-in-a-mist", species: "Nigella damascena", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Chamomile, German", species: "Matricaria chamomilla", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Phacelia", species: "Phacelia tanacetifolia", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Scabiosa", species: "Scabiosa atropurpurea", category: "ANNUAL_FLOWER" },
|
||||||
|
{ commonName: "Lisianthus", species: "Eustoma grandiflorum", category: "ANNUAL_FLOWER" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Bulbs
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Garlic", species: "Allium sativum", category: "BULB" },
|
||||||
|
{ commonName: "Tulip", species: "Tulipa spp.", category: "BULB" },
|
||||||
|
{ commonName: "Daffodil", species: "Narcissus spp.", category: "BULB" },
|
||||||
|
{ commonName: "Crocus", species: "Crocus spp.", category: "BULB" },
|
||||||
|
{ commonName: "Allium, ornamental", species: "Allium spp.", category: "BULB" },
|
||||||
|
{ commonName: "Hyacinth", species: "Hyacinthus orientalis", category: "BULB" },
|
||||||
|
{ commonName: "Lily", species: "Lilium spp.", category: "BULB" },
|
||||||
|
{ commonName: "Dahlia", species: "Dahlia spp.", category: "BULB" },
|
||||||
|
{ commonName: "Gladiolus", species: "Gladiolus spp.", category: "BULB" },
|
||||||
|
{ commonName: "Camassia", species: "Camassia spp.", category: "BULB" },
|
||||||
|
{ commonName: "Fritillaria", species: "Fritillaria spp.", category: "BULB" },
|
||||||
|
{ commonName: "Scilla", species: "Scilla siberica", category: "BULB" },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Mushrooms
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
{ commonName: "Shiitake", species: "Lentinula edodes", category: "MUSHROOM", notes: "Inoculated log. Likes oak, hornbeam, or ironwood logs." },
|
||||||
|
{ commonName: "Oyster mushroom", species: "Pleurotus ostreatus", category: "MUSHROOM" },
|
||||||
|
{ commonName: "Wine cap", species: "Stropharia rugosoannulata", category: "MUSHROOM", notes: "Wood chip bed. Great for the garden paths." },
|
||||||
|
{ commonName: "Lion's mane", species: "Hericium erinaceus", category: "MUSHROOM" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Search the database by common name (case-insensitive, partial match).
|
||||||
|
// Returns up to 8 results, sorted so exact starts-with matches come first.
|
||||||
|
export function searchPlants(query: string): PlantSuggestion[] {
|
||||||
|
if (!query || query.trim().length < 2) return [];
|
||||||
|
const q = query.toLowerCase().trim();
|
||||||
|
const results = PLANT_DB.filter((p) =>
|
||||||
|
p.commonName.toLowerCase().includes(q)
|
||||||
|
);
|
||||||
|
results.sort((a, b) => {
|
||||||
|
const aStarts = a.commonName.toLowerCase().startsWith(q) ? 0 : 1;
|
||||||
|
const bStarts = b.commonName.toLowerCase().startsWith(q) ? 0 : 1;
|
||||||
|
return aStarts - bStarts || a.commonName.localeCompare(b.commonName);
|
||||||
|
});
|
||||||
|
return results.slice(0, 8);
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
// Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`.
|
// Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`.
|
||||||
export const APP_VERSION = "0.1.1";
|
export const APP_VERSION = "0.2.0";
|
||||||
|
|||||||
Reference in New Issue
Block a user