Implement activity log (task 22)
- activity_log table in _core/schema.ts with household+created_at index; migration 0008_activity_log.sql applied - logActivity() in _core/activity.ts reads current session and inserts a row - ActivityLogEntry type + renderActivity?(entry): string added to EntityTypeRegistration so modules declare human-readable labels without any if/else branches in the widget - core.activity widget replaced with a real async server component that queries the last 20 rows and renders via the registry - logActivity wired into every create/update/delete in calendar, lists, and notes server actions; getAuthorizedItem also returns text so toggle/delete can include item text in the payload - pnpm typecheck, lint, build, and all 4 E2E specs pass Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1133fa8aac
commit
28475e483d
@@ -1,5 +1,49 @@
|
||||
import type { ModuleManifest } from "./module";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import { getCurrentSession } from "@/lib/session";
|
||||
import type { ActivityLogEntry, ModuleManifest } from "./module";
|
||||
import { getEntityType } from "./registry";
|
||||
import { activityLog } from "./schema";
|
||||
|
||||
const activityConfigSchema = z.object({
|
||||
limit: z.number().int().min(1).max(50).optional(),
|
||||
});
|
||||
|
||||
async function ActivityWidget({ config }: { config: unknown }) {
|
||||
const parsed = activityConfigSchema.parse(config);
|
||||
const { household } = await getCurrentSession();
|
||||
|
||||
const entries = await db
|
||||
.select()
|
||||
.from(activityLog)
|
||||
.where(eq(activityLog.householdId, household.id))
|
||||
.orderBy(desc(activityLog.createdAt))
|
||||
.limit(parsed.limit ?? 20);
|
||||
|
||||
if (entries.length === 0) {
|
||||
return <p className="text-sm text-muted-foreground">No recent activity</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="space-y-2">
|
||||
{entries.map((entry) => {
|
||||
const reg = getEntityType(entry.entityType);
|
||||
const description =
|
||||
reg?.renderActivity?.(entry as ActivityLogEntry) ??
|
||||
`${entry.action} ${entry.entityType}`;
|
||||
return (
|
||||
<li key={entry.id} className="flex items-start gap-2 text-sm">
|
||||
<span className="mt-0.5 shrink-0 text-xs text-muted-foreground">
|
||||
{entry.createdAt.toLocaleDateString(undefined, { month: "short", day: "numeric" })}
|
||||
</span>
|
||||
<span className="leading-snug">{description}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
const coreManifest: ModuleManifest = {
|
||||
id: "_core",
|
||||
@@ -14,16 +58,10 @@ const coreManifest: ModuleManifest = {
|
||||
defaultSize: { w: 4, h: 3 },
|
||||
minSize: { w: 3, h: 2 },
|
||||
defaultPriority: 50,
|
||||
configSchema: z.object({
|
||||
limit: z.number().int().min(1).max(50).optional(),
|
||||
}),
|
||||
defaultConfig: { limit: 10 },
|
||||
configSchema: activityConfigSchema,
|
||||
defaultConfig: { limit: 20 },
|
||||
resolveConfigOptions: async () => undefined,
|
||||
render: () => (
|
||||
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||
Activity log coming in task 22
|
||||
</div>
|
||||
),
|
||||
render: (props) => <ActivityWidget {...props} />,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user