feat: copyable task IDs on cards and detail panel

This commit is contained in:
2026-01-28 23:46:57 +00:00
parent aea54516c9
commit 186a565bee
2 changed files with 63 additions and 4 deletions

View File

@@ -1,3 +1,4 @@
import { useState } from "react";
import type { Task, TaskStatus, TaskPriority } from "../lib/types"; import type { Task, TaskStatus, TaskPriority } from "../lib/types";
const priorityColors: Record<TaskPriority, string> = { const priorityColors: Record<TaskPriority, string> = {
@@ -65,8 +66,18 @@ export function TaskCard({
isActive, isActive,
onClick, onClick,
}: TaskCardProps) { }: TaskCardProps) {
const [idCopied, setIdCopied] = useState(false);
const actions = statusActions[task.status] || []; const actions = statusActions[task.status] || [];
const noteCount = task.progressNotes?.length || 0; const noteCount = task.progressNotes?.length || 0;
const shortId = task.id.slice(0, 8);
const handleCopyId = (e: React.MouseEvent) => {
e.stopPropagation();
navigator.clipboard.writeText(task.id).then(() => {
setIdCopied(true);
setTimeout(() => setIdCopied(false), 1500);
});
};
return ( return (
<div <div
@@ -92,6 +103,16 @@ export function TaskCard({
</div> </div>
<div className="flex items-center gap-2 mb-2 flex-wrap"> <div className="flex items-center gap-2 mb-2 flex-wrap">
<span
className="text-xs px-1.5 py-0.5 rounded font-mono bg-gray-100 text-gray-500 cursor-pointer hover:bg-gray-200 transition"
title={`Click to copy: ${task.id}`}
onClick={(e) => {
e.stopPropagation();
navigator.clipboard.writeText(task.id);
}}
>
{task.id.slice(0, 8)}
</span>
<span className={`text-xs px-2 py-0.5 rounded-full font-medium ${priorityColors[task.priority]}`}> <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${priorityColors[task.priority]}`}>
{task.priority} {task.priority}
</span> </span>
@@ -106,6 +127,17 @@ export function TaskCard({
💬 {noteCount} 💬 {noteCount}
</span> </span>
)} )}
<button
onClick={handleCopyId}
className="text-xs font-mono text-gray-300 hover:text-amber-600 transition cursor-pointer ml-auto"
title={`Copy full ID: ${task.id}`}
>
{idCopied ? (
<span className="text-green-500"> copied</span>
) : (
<span>{shortId}</span>
)}
</button>
</div> </div>
{task.description && ( {task.description && (

View File

@@ -118,6 +118,35 @@ function ElapsedTimer({ since }: { since: string }) {
); );
} }
function CopyableId({ id }: { id: string }) {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
navigator.clipboard.writeText(id).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
};
return (
<div className="px-6 py-3 border-t border-gray-100 bg-gray-50 flex items-center gap-2">
<span className="text-xs text-gray-400">ID:</span>
<code className="text-xs text-gray-400 font-mono flex-1 truncate select-all">{id}</code>
<button
onClick={handleCopy}
className={`text-xs px-2.5 py-1 rounded-md border transition font-medium ${
copied
? "bg-green-50 text-green-600 border-green-200"
: "bg-white text-gray-500 border-gray-200 hover:bg-gray-100 hover:text-gray-700"
}`}
title="Copy task ID"
>
{copied ? "✓ Copied" : "📋 Copy"}
</button>
</div>
);
}
interface TaskDetailPanelProps { interface TaskDetailPanelProps {
task: Task; task: Task;
onClose: () => void; onClose: () => void;
@@ -278,10 +307,8 @@ export function TaskDetailPanel({ task, onClose, onStatusChange, hasToken }: Tas
</div> </div>
)} )}
{/* Task ID */} {/* Task ID - click to copy */}
<div className="px-6 py-2 border-t border-gray-100 bg-gray-50"> <CopyableId id={task.id} />
<p className="text-xs text-gray-300 font-mono truncate">ID: {task.id}</p>
</div>
</div> </div>
</> </>
); );