Implement tasks 25, 26 + completion visibility setting

Task 25: Multiple dashboards — dashboards table + migration, /d/[slug]
route, root redirect, dashboard tabs in nav with create/rename/delete/
set-default. Task 26: Editable dashboard — react-grid-layout drag/resize
editor, widget picker modal with per-widget configurator auto-generated
from default config, save/reset server actions with Zod validation.

Completion visibility: server-side per-user setting (hours) replaces
localStorage timer; queries filter completed items by updatedAt cutoff;
Settings page dropdown persists preference via server action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ginnoir
2026-05-06 15:52:59 -05:00
co-authored by Claude Sonnet 4.6
parent 2ad9521ef9
commit d5a8bf9d95
27 changed files with 1646 additions and 237 deletions
+35 -21
View File
@@ -1,32 +1,46 @@
import Link from "next/link";
import { Settings } from "lucide-react";
import { getRegistry } from "@/modules/_core/registry";
import type { DashboardMeta } from "@/app/d/actions";
import { DashboardSwitcher } from "./dashboard-switcher";
import { DashboardTab } from "./dashboard-tab";
export function AppNav() {
export function AppNav({ dashboards = [] }: { dashboards?: DashboardMeta[] }) {
const { modules } = getRegistry();
const navItems = modules.flatMap((m) => (m.nav ? [m.nav] : []));
return (
<nav className="border-b px-4 py-3 flex items-center gap-6">
<Link href="/" className="font-semibold text-sm">
famapp
</Link>
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
{item.label}
<header className="border-b">
<nav className="px-4 py-3 flex items-center gap-6">
<Link href="/" className="font-semibold text-sm shrink-0">
famapp
</Link>
))}
<Link
href="/settings"
className="ml-auto text-muted-foreground hover:text-foreground transition-colors"
aria-label="Settings"
>
<Settings className="size-4" />
</Link>
</nav>
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
{item.label}
</Link>
))}
<Link
href="/settings"
className="ml-auto text-muted-foreground hover:text-foreground transition-colors"
aria-label="Settings"
>
<Settings className="size-4" />
</Link>
</nav>
{dashboards.length > 0 && (
<div className="flex items-center gap-1 border-t px-4 overflow-x-auto">
{dashboards.map((d) => (
<DashboardTab key={d.id} slug={d.slug} name={d.name} />
))}
<DashboardSwitcher dashboards={dashboards} />
</div>
)}
</header>
);
}