"use client"; import { useState } from "react"; import { toast } from "sonner"; import { Check, Copy, FileText, Loader2, Sparkles } from "lucide-react"; import { colorModeFromTheme } from "@/components/theme/use-dark-theme"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { MarkdownPreview } from "@/components/markdown/markdown-widgets"; import { summarizeBoard } from "@/lib/actions/summarize"; import { toDateKey } from "@/lib/dates"; const DATE_RANGE_OPTIONS = [ { value: "today", label: "Today" }, { value: "thisWeek", label: "This Week" }, { value: "thisMonth", label: "This Month" }, { value: "lastMonth", label: "Last Month" }, { value: "custom", label: "Custom Range" }, ] as const; const ORGANIZE_BY_OPTIONS = [ { value: "byDate", label: "By Date" }, { value: "byCategory", label: "By Category/Group" }, ] as const; type DateRange = (typeof DATE_RANGE_OPTIONS)[number]["value"]; type OrganizeBy = (typeof ORGANIZE_BY_OPTIONS)[number]["value"]; /** * Board header's "Summary" button: a compact popover with two dropdowns * (date range + how to organize) and a Summarize action, then a * result dialog that renders the AI's markdown recap with a copy-to- * clipboard icon. Scoped to whatever board it's mounted on (Home or a * Project) via `projectId`. */ export function SummaryButton({ projectId }: { projectId?: string }) { const colorMode = colorModeFromTheme(); // Popover (selection) state. const [popoverOpen, setPopoverOpen] = useState(false); const [dateRange, setDateRange] = useState("today"); const [organizeBy, setOrganizeBy] = useState("byDate"); // Both default to today so a "Custom Range" pick is immediately usable. const [customStart, setCustomStart] = useState(() => toDateKey(new Date())); const [customEnd, setCustomEnd] = useState(() => toDateKey(new Date())); // Result dialog state. const [resultOpen, setResultOpen] = useState(false); const [generating, setGenerating] = useState(false); const [error, setError] = useState(null); const [noWork, setNoWork] = useState(false); const [summary, setSummary] = useState(null); const [rangeLabel, setRangeLabel] = useState(null); const [count, setCount] = useState(0); const [copied, setCopied] = useState(false); const customInvalid = dateRange === "custom" && (!customStart || !customEnd || customStart > customEnd); async function handleSummarize() { if (generating || customInvalid) return; setPopoverOpen(false); setResultOpen(true); setGenerating(true); setError(null); setNoWork(false); setSummary(null); setRangeLabel(null); setCount(0); setCopied(false); try { const result = await summarizeBoard(projectId ?? null, { dateRange, customStart: dateRange === "custom" ? customStart : undefined, customEnd: dateRange === "custom" ? customEnd : undefined, organizeBy, }); if (result.status === "ok") { setSummary(result.content); setRangeLabel(result.rangeLabel); setCount(result.count); } else if (result.status === "empty") { setRangeLabel(result.rangeLabel); setNoWork(true); } else { throw new Error(result.error); } } catch (err) { setError(err instanceof Error ? err.message : "Something went wrong."); } finally { setGenerating(false); } } // Starts fresh every time it's reopened -- a summary from a different // range would be stale the moment the dialog closes and reopens. function handleResultOpenChange(next: boolean) { if (!next) { setGenerating(false); setError(null); setNoWork(false); setSummary(null); setRangeLabel(null); setCount(0); setCopied(false); } setResultOpen(next); } async function handleCopy() { if (!summary) return; try { await navigator.clipboard.writeText(summary); setCopied(true); toast.success("Summary copied to clipboard."); setTimeout(() => setCopied(false), 2000); } catch { toast.error("Couldn't copy to clipboard."); } } return ( <> Summary } /> Summarize completed work An AI recap of what you got done on this board.
{dateRange === "custom" && (
setCustomStart(e.target.value)} />
setCustomEnd(e.target.value)} />
)}
{customInvalid && (

Pick a start date on or before the end date.

)}
Summary {rangeLabel ? `${rangeLabel} · ${count} completed ${count === 1 ? "item" : "items"}` : "An AI recap of what you got done."} {generating && (

Summarizing your completed work…

)} {!generating && error && (

{error}

)} {!generating && !error && noWork && (

Nothing was completed in this range yet -- check something off first.

)} {!generating && !error && !noWork && summary && (
)} {summary && (
{copied ? : } } /> {copied ? "Copied" : "Copy to clipboard"}
)}
); }