feat: garden containers crud (task 71)

- add listContainers/getContainer/searchContainers queries
- add createContainer/updateContainer/deleteContainer server actions with logActivity
- add ContainerList, ContainerDetail, ContainerForm client components
- add /garden, /garden/containers/[id], /garden/containers/new pages
- register garden.container entity with share + search in manifest
- add sprout icon to NavIcon registry
- add garden e2e test spec
This commit is contained in:
ginnoir
2026-06-01 19:28:34 -05:00
parent 2fd0677c5f
commit 5f6b756342
12 changed files with 756 additions and 4 deletions
+53 -2
View File
@@ -1,12 +1,63 @@
import type { ModuleManifest } from "../_core/module";
import { loadContainerForShare, type ContainerShareData } from "./server/share-queries";
import { searchContainers } from "./server/queries";
const gardenManifest: ModuleManifest = {
id: "garden",
name: "Garden",
nav: { href: "/garden", label: "Garden", icon: "sprout" },
entities: [],
entities: [
{
type: "garden.container",
label: { singular: "Container", plural: "Containers" },
share: { canShare: true, defaultCapabilities: ["read"] },
search: { search: searchContainers },
resolveUrl: (id) => `/garden/containers/${id}`,
loadForShare: (id) => loadContainerForShare(id),
renderSharedView: ({ data }) => {
const d = data as ContainerShareData;
return (
<div className="flex flex-col gap-3">
<h2 className="text-xl font-bold">{d.name}</h2>
<p className="text-sm capitalize text-[var(--ink-mute)]">{d.type}</p>
{d.locationNotes && <p className="text-sm">{d.locationNotes}</p>}
{d.plants.length > 0 && (
<div>
<p className="text-sm font-medium mb-1">Plants</p>
<ul className="text-sm space-y-1">
{d.plants.map((p) => (
<li key={p.id}>
{p.name}
{p.scientificName && (
<span className="text-[var(--ink-mute)] italic ml-1">
({p.scientificName})
</span>
)}
</li>
))}
</ul>
</div>
)}
</div>
);
},
renderActivity: (entry) => {
const name = entry.payload?.name as string | undefined;
if (entry.action === "create") return `Created container${name ? ` "${name}"` : ""}`;
if (entry.action === "delete") return "Deleted container";
return `Updated container${name ? ` "${name}"` : ""}`;
},
},
],
dashboardWidgets: [],
quickAdds: [],
quickAdds: [
{
id: "garden.add-container",
label: "Add container",
icon: "sprout",
url: "/garden/containers/new",
},
],
};
export default gardenManifest;