fix: dashboard edit previews, migration journal, and notes comments
CI / checks (push) Failing after 2m8s
CI / build (push) Successful in 4m42s

Register drizzle journal entries for 0022/0023 so prod migrations apply.

Key edit-mode widget previews by index so bangs.stats shows live data.

Add comments to notes detail pages.
This commit is contained in:
ginnoir
2026-07-04 22:39:21 -05:00
parent 9e66c12eb7
commit 6183fb62c8
11 changed files with 72 additions and 17 deletions
+4 -4
View File
@@ -1,7 +1,7 @@
import { notFound } from "next/navigation";
import { Suspense } from "react";
import { getCurrentSession } from "@/lib/session";
import { parseDashboardLayout, widgetContentKey } from "@/lib/dashboard";
import { parseDashboardLayout, widgetContentIndexKey } from "@/lib/dashboard";
import { readEditorDraftLayout } from "@/lib/dashboard-editor-draft";
import { computeDefaultLayout, normalizeDashboardLayout } from "@/lib/dashboard.server";
import { getWidget, getWidgetMetas } from "@/modules/_core";
@@ -58,10 +58,10 @@ export default async function DashboardPage({
if (isEditing) {
const ctx = { userId: user.id, householdId: household.id };
const widgetContents = Object.fromEntries(
layout.widgets.map((placement) => [
widgetContentKey(placement),
layout.widgets.map((placement, index) => [
widgetContentIndexKey(index),
<DashboardWidgetContent
key={widgetContentKey(placement)}
key={widgetContentIndexKey(index)}
placement={placement}
ctx={ctx}
/>,
+3 -2
View File
@@ -1,11 +1,12 @@
import { notFound } from "next/navigation";
import { getCurrentSession } from "@/lib/session";
import { NoteEditor } from "@/modules/notes/components/note-editor";
import { getNote } from "@/modules/notes/server/queries";
export default async function NotePage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const note = await getNote(id).catch(() => null);
const [{ user }, note] = await Promise.all([getCurrentSession(), getNote(id).catch(() => null)]);
if (!note) notFound();
return <NoteEditor note={note} />;
return <NoteEditor note={note} currentUserId={user.id} />;
}
+24 -4
View File
@@ -9,7 +9,7 @@ import { GridLayout } from "react-grid-layout";
import type { Layout } from "react-grid-layout";
import { GripVertical, Settings2, Trash2, RotateCcw, Plus, LayoutGrid } from "lucide-react";
import type { DashboardLayout, WidgetPlacement, PresetId } from "@/lib/dashboard";
import { computePresetLayoutFromMetas, widgetContentKey } from "@/lib/dashboard";
import { computePresetLayoutFromMetas, widgetContentIndexKey } from "@/lib/dashboard";
import type { SerializedWidgetMeta } from "@/modules/_core/registry";
import {
saveDashboardLayout,
@@ -113,10 +113,18 @@ export function DashboardEditor({
function addWidget(widgetId: string, config: unknown) {
const meta = widgetMetas.find((m) => m.id === widgetId);
if (!meta) return;
const resolvedConfig = resolveWidgetConfig(meta, config);
const maxY = placements.reduce((m, p) => Math.max(m, p.y + p.h), 0);
const next = [
...placements,
{ widgetId, config, x: 0, y: maxY, w: meta.defaultSize.w, h: meta.defaultSize.h },
{
widgetId,
config: resolvedConfig,
x: 0,
y: maxY,
w: meta.defaultSize.w,
h: meta.defaultSize.h,
},
];
setPlacements(next);
setIsDirty(true);
@@ -125,7 +133,9 @@ export function DashboardEditor({
}
function updateConfig(index: number, config: unknown) {
const next = placements.map((p, i) => (i === index ? { ...p, config } : p));
const meta = widgetMetas.find((m) => m.id === placements[index]?.widgetId);
const resolvedConfig = meta ? resolveWidgetConfig(meta, config) : config;
const next = placements.map((p, i) => (i === index ? { ...p, config: resolvedConfig } : p));
setPlacements(next);
setIsDirty(true);
setConfiguringIndex(null);
@@ -237,7 +247,7 @@ export function DashboardEditor({
>
{placements.map((placement, i) => {
const meta = widgetMetas.find((m) => m.id === placement.widgetId);
const content = widgetContents[widgetContentKey(placement)];
const content = widgetContents[widgetContentIndexKey(i)];
return (
<div
key={placementKey(placement, i)}
@@ -308,3 +318,13 @@ export function DashboardEditor({
</div>
);
}
function resolveWidgetConfig(meta: SerializedWidgetMeta, config: unknown): unknown {
if (config != null && typeof config === "object" && !Array.isArray(config)) {
return {
...(meta.defaultConfig as Record<string, unknown>),
...(config as Record<string, unknown>),
};
}
return meta.defaultConfig;
}
+6 -1
View File
@@ -11,7 +11,12 @@ export type WidgetPlacement = {
};
export function widgetContentKey(placement: Pick<WidgetPlacement, "widgetId" | "config">): string {
return `${placement.widgetId}::${JSON.stringify(placement.config)}`;
return `${placement.widgetId}::${JSON.stringify(placement.config ?? null)}`;
}
/** Stable key for edit-mode widget preview map (index-aligned with layout.widgets). */
export function widgetContentIndexKey(index: number): string {
return String(index);
}
export type DashboardLayout = {
+2
View File
@@ -77,6 +77,7 @@ export async function addComment(input: z.input<typeof commentInput>): Promise<C
if (!row) throw new Error("Comment was not created");
revalidatePath("/lists");
revalidatePath("/notes");
return {
id: row.id,
@@ -104,4 +105,5 @@ export async function deleteComment(input: { id: string }) {
await db.delete(comments).where(eq(comments.id, parsed.id));
revalidatePath("/lists");
revalidatePath("/notes");
}
+3 -1
View File
@@ -16,7 +16,8 @@ async function BangWidgetServer({ config, ctx }: { config: unknown; ctx: WidgetC
return <BangWidget stats={stats} maxRecentBangs={parsed.maxRecentBangs} />;
}
async function BangStatsWidgetServer({ ctx }: { config: unknown; ctx: WidgetContext }) {
async function BangStatsWidgetServer({ config, ctx }: { config: unknown; ctx: WidgetContext }) {
bangStatsConfigSchema.parse(config);
const aggregates = await getBangAggregates(ctx.householdId);
return <BangStatsWidget aggregates={aggregates} />;
}
@@ -62,6 +63,7 @@ const bangsManifest: ModuleManifest = {
defaultPriority: 55,
configSchema: bangStatsConfigSchema,
defaultConfig: {},
resolveConfigOptions: async () => undefined,
render: (props) => <BangStatsWidgetServer {...props} />,
},
],
+10 -1
View File
@@ -4,6 +4,7 @@ import dynamic from "next/dynamic";
import { useRouter } from "next/navigation";
import { Pin, PinOff, Save, Trash2 } from "lucide-react";
import { useState, useTransition } from "react";
import { EntityComments } from "@/components/comments/entity-comments";
import { RichTextContent } from "@/components/rich-text";
import { DetailBackLink } from "@/components/detail-back-link";
import { Button } from "@/components/ui/button";
@@ -25,7 +26,7 @@ const RichTextEditor = dynamic(
},
);
export function NoteEditor({ note }: { note?: NoteDto }) {
export function NoteEditor({ note, currentUserId }: { note?: NoteDto; currentUserId?: string }) {
const router = useRouter();
const [currentNote, setCurrentNote] = useState(note);
const [title, setTitle] = useState(note?.title ?? "");
@@ -147,6 +148,14 @@ export function NoteEditor({ note }: { note?: NoteDto }) {
<RichTextContent html={body} />
</aside>
</div>
{currentNote && currentUserId ? (
<EntityComments
entityType="notes.note"
entityId={currentNote.id}
currentUserId={currentUserId}
/>
) : null}
</div>
);
}