feat: garden dashboard widgets and manifest completion (task 75)
- getCareDueWidgetRows query: schedule rows due within N days, filtered by optional container list, ordered by next_due_at - getGardenOverviewStats query: plant/container/overdue counts + next care - CareDueWidget: compact list sorted by urgency, inline log-care button, truncates at 10 rows with View all link - GardenOverviewWidget: stat row (plants, containers, overdue) + next care line + garden link - plant-widget.tsx server component: InlineLogButton uses form action to call logCare without client JS or navigation - manifest: garden.care-due and garden.overview widgets registered; resolveConfigOptions returns container list for care-due config picker - quickAdds: icons updated (leaf/box/droplets), log-care URL -> /garden?logCare=1
This commit is contained in:
@@ -1,11 +1,35 @@
|
||||
import type { ModuleManifest } from "../_core/module";
|
||||
import { z } from "zod";
|
||||
import type { ModuleManifest, WidgetContext } from "../_core/module";
|
||||
import {
|
||||
loadContainerForShare,
|
||||
loadPlantForShare,
|
||||
type ContainerShareData,
|
||||
type PlantShareData,
|
||||
} from "./server/share-queries";
|
||||
import { searchContainers, searchPlants } from "./server/queries";
|
||||
import {
|
||||
listContainers,
|
||||
searchContainers,
|
||||
searchPlants,
|
||||
getCareDueWidgetRows,
|
||||
getGardenOverviewStats,
|
||||
} from "./server/queries";
|
||||
import { CareDueWidget, GardenOverviewWidget } from "./components/plant-widget";
|
||||
|
||||
const careDueConfigSchema = z.object({
|
||||
containerIds: z.union([z.literal("all"), z.array(z.string().uuid())]),
|
||||
daysAhead: z.number().int().min(0).max(30).default(0),
|
||||
});
|
||||
|
||||
async function CareDueWidgetServer({ config, ctx }: { config: unknown; ctx: WidgetContext }) {
|
||||
const parsed = careDueConfigSchema.parse(config);
|
||||
const rows = await getCareDueWidgetRows(ctx.householdId, parsed.containerIds, parsed.daysAhead);
|
||||
return <CareDueWidget rows={rows} />;
|
||||
}
|
||||
|
||||
async function GardenOverviewWidgetServer({ ctx }: { config: unknown; ctx: WidgetContext }) {
|
||||
const stats = await getGardenOverviewStats(ctx.householdId);
|
||||
return <GardenOverviewWidget stats={stats} />;
|
||||
}
|
||||
|
||||
const gardenManifest: ModuleManifest = {
|
||||
id: "garden",
|
||||
@@ -118,25 +142,53 @@ const gardenManifest: ModuleManifest = {
|
||||
},
|
||||
},
|
||||
],
|
||||
dashboardWidgets: [],
|
||||
dashboardWidgets: [
|
||||
{
|
||||
id: "garden.care-due",
|
||||
title: "Plants needing care",
|
||||
description: "Shows plants with overdue or upcoming care schedules.",
|
||||
category: "Garden",
|
||||
defaultSize: { w: 4, h: 4 },
|
||||
minSize: { w: 3, h: 2 },
|
||||
defaultPriority: 40,
|
||||
configSchema: careDueConfigSchema,
|
||||
defaultConfig: { containerIds: "all", daysAhead: 0 },
|
||||
resolveConfigOptions: async () => ({
|
||||
containers: (await listContainers()).map((c) => ({ id: c.id, name: c.name })),
|
||||
}),
|
||||
render: (props) => <CareDueWidgetServer {...props} />,
|
||||
},
|
||||
{
|
||||
id: "garden.overview",
|
||||
title: "Garden overview",
|
||||
description: "Plant and container counts with next care summary.",
|
||||
category: "Garden",
|
||||
defaultSize: { w: 3, h: 2 },
|
||||
minSize: { w: 2, h: 2 },
|
||||
defaultPriority: 41,
|
||||
configSchema: z.object({}),
|
||||
defaultConfig: {},
|
||||
render: (props) => <GardenOverviewWidgetServer {...props} />,
|
||||
},
|
||||
],
|
||||
quickAdds: [
|
||||
{
|
||||
id: "garden.add-plant",
|
||||
label: "Add plant",
|
||||
icon: "sprout",
|
||||
icon: "leaf",
|
||||
url: "/garden/plants/new",
|
||||
},
|
||||
{
|
||||
id: "garden.add-container",
|
||||
label: "Add container",
|
||||
icon: "sprout",
|
||||
icon: "box",
|
||||
url: "/garden/containers/new",
|
||||
},
|
||||
{
|
||||
id: "garden.log-care",
|
||||
label: "Log plant care",
|
||||
icon: "droplets",
|
||||
url: "/garden",
|
||||
url: "/garden?logCare=1",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user