From 2f55ca01141ce4353a53e6e43c3913cffd5362c3 Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 19 Sep 2026 13:19:02 +0200 Subject: [PATCH] feat(dashboard): Log work button and form Co-Authored-By: Claude Haiku 4.5 --- .../[projectSlug]/dashboard/page.tsx | 12 ++ src/components/dashboard/log-work-panel.tsx | 191 ++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/components/dashboard/log-work-panel.tsx diff --git a/src/app/p/[clientSlug]/[projectSlug]/dashboard/page.tsx b/src/app/p/[clientSlug]/[projectSlug]/dashboard/page.tsx index 48509f1..2c493df 100644 --- a/src/app/p/[clientSlug]/[projectSlug]/dashboard/page.tsx +++ b/src/app/p/[clientSlug]/[projectSlug]/dashboard/page.tsx @@ -12,6 +12,8 @@ import { Methodology } from "@/components/dashboard/methodology"; import { RecentActivity } from "@/components/dashboard/recent-activity"; import { RecomputeButton } from "@/components/dashboard/recompute-button"; import { RefreshCommitsButton } from "@/components/dashboard/refresh-commits-button"; +import { aiConfigured } from "@/lib/ai/settings"; +import { LogWorkPanel } from "@/components/dashboard/log-work-panel"; import { getSession } from "@/lib/auth/session"; import { getServerPb } from "@/lib/pb/server"; import { loadBoard } from "@/lib/data/board"; @@ -88,6 +90,16 @@ export default async function DashboardPage({ )} + {session.role === "owner" && ( + + )} + {!metrics || metrics.totalCommits === 0 ? (

diff --git a/src/components/dashboard/log-work-panel.tsx b/src/components/dashboard/log-work-panel.tsx new file mode 100644 index 0000000..828d843 --- /dev/null +++ b/src/components/dashboard/log-work-panel.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { Plus, Sparkles } from "lucide-react"; + +import { logWorkAction, polishNoteAction } from "@/app/actions/log-work"; +import type { CardRecord } from "@/lib/pb/types"; + +/** + * The owner's way to put off-repo work onto the dashboard. + * + * Follows the same inline-expand pattern as NewProjectForm and OwnerAdd + * elsewhere in this app, rather than introducing a modal/dialog component + * that does not otherwise exist in the codebase. + */ +export function LogWorkPanel({ + projectId, + clientSlug, + projectSlug, + cards, + assistantAvailable, +}: { + projectId: string; + clientSlug: string; + projectSlug: string; + cards: CardRecord[]; + assistantAvailable: boolean; +}) { + const [open, setOpen] = useState(false); + const [date, setDate] = useState(() => new Date().toISOString().slice(0, 10)); + const [hours, setHours] = useState(""); + const [message, setMessage] = useState(""); + const [cardId, setCardId] = useState(""); + const [pending, startTransition] = useTransition(); + const [polishing, setPolishing] = useState(false); + const [error, setError] = useState(null); + + if (!open) { + return ( + + ); + } + + const field = + "w-full rounded-lg border border-border-subtle bg-panel px-3 py-2 text-sm text-text-primary outline-none focus:border-brand"; + + function submit(event: React.FormEvent) { + event.preventDefault(); + const parsedHours = Number(hours); + if (!message.trim() || !Number.isFinite(parsedHours) || parsedHours <= 0) return; + setError(null); + + startTransition(async () => { + const result = await logWorkAction({ + projectId, + clientSlug, + projectSlug, + date, + hours: parsedHours, + message, + cardId: cardId || undefined, + }); + + if (!result.ok) { + setError(result.message ?? "Could not log that work"); + return; + } + + setOpen(false); + setHours(""); + setMessage(""); + setCardId(""); + }); + } + + async function polish() { + if (!message.trim()) return; + setPolishing(true); + setError(null); + try { + const result = await polishNoteAction({ text: message }); + if (result.ok && result.text) setMessage(result.text); + else setError(result.message ?? "Could not polish that note"); + } finally { + setPolishing(false); + } + } + + return ( +
+
+ + + +
+ +