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>
47 lines
1.5 KiB
TypeScript
47 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { useTransition } from "react";
|
|
import { setCompletionVisibilityHours } from "@/app/settings/actions";
|
|
|
|
const OPTIONS = [
|
|
{ label: "1 hour", value: 1 },
|
|
{ label: "4 hours", value: 4 },
|
|
{ label: "8 hours", value: 8 },
|
|
{ label: "24 hours (default)", value: 24 },
|
|
{ label: "48 hours", value: 48 },
|
|
{ label: "7 days", value: 168 },
|
|
{ label: "Never hide", value: 8760 },
|
|
];
|
|
|
|
export function CompletionDelaySetting({ initialHours }: { initialHours: number }) {
|
|
const [isPending, startTransition] = useTransition();
|
|
|
|
function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
|
const hours = Number(e.target.value);
|
|
startTransition(() => setCompletionVisibilityHours(hours));
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center justify-between gap-4">
|
|
<div>
|
|
<p className="text-sm font-medium">Show completed items for</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
How long checked-off items remain visible on list cards and the dashboard.
|
|
</p>
|
|
</div>
|
|
<select
|
|
defaultValue={initialHours}
|
|
onChange={handleChange}
|
|
disabled={isPending}
|
|
className="rounded-md border border-input bg-background px-3 py-1.5 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50"
|
|
>
|
|
{OPTIONS.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
);
|
|
}
|