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:
@@ -0,0 +1,14 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { getContainer } from "@/modules/garden/server/queries";
|
||||
import { ContainerDetail } from "@/modules/garden/components/container-detail";
|
||||
|
||||
export default async function ContainerPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const container = await getContainer(id);
|
||||
if (!container) notFound();
|
||||
return (
|
||||
<div className="page-content">
|
||||
<ContainerDetail container={container} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { createContainer } from "@/modules/garden/server/actions";
|
||||
|
||||
export default function NewContainerPage() {
|
||||
async function handleCreate(formData: FormData) {
|
||||
"use server";
|
||||
await createContainer({
|
||||
name: formData.get("name") as string,
|
||||
type: (formData.get("type") as string) || "other",
|
||||
locationNotes: (formData.get("locationNotes") as string) || null,
|
||||
});
|
||||
redirect("/garden");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page-content max-w-lg">
|
||||
<h1 className="page-title">New container</h1>
|
||||
<form action={handleCreate} className="flex flex-col gap-4 mt-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="name" className="text-sm font-medium">
|
||||
Name
|
||||
</label>
|
||||
<input id="name" name="name" required maxLength={120} className="input" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="type" className="text-sm font-medium">
|
||||
Type
|
||||
</label>
|
||||
<select id="type" name="type" defaultValue="other">
|
||||
{(
|
||||
[
|
||||
["shelf", "Shelf"],
|
||||
["terrarium", "Terrarium"],
|
||||
["raised-bed", "Raised bed"],
|
||||
["window-box", "Window box"],
|
||||
["single-pot", "Single pot"],
|
||||
["outdoor", "Outdoor"],
|
||||
["other", "Other"],
|
||||
] as const
|
||||
).map(([v, l]) => (
|
||||
<option key={v} value={v}>
|
||||
{l}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="locationNotes" className="text-sm font-medium">
|
||||
Location notes
|
||||
</label>
|
||||
<textarea
|
||||
id="locationNotes"
|
||||
name="locationNotes"
|
||||
maxLength={500}
|
||||
rows={2}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2 justify-end">
|
||||
<a href="/garden" className="btn btn-ghost">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user