feat: copyable task IDs on cards and detail panel
This commit is contained in:
@@ -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 && (
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user