feat(19-02): create MessageCard component for agent inbox
MessageCard displays agent name with status, message preview (truncated to 80 chars), relative timestamp, and response indicator (filled/empty circle). Uses shadcn Card base with selected state highlighting.
This commit is contained in:
83
packages/web/src/components/MessageCard.tsx
Normal file
83
packages/web/src/components/MessageCard.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function formatRelativeTime(isoDate: string): string {
|
||||
const now = Date.now();
|
||||
const then = new Date(isoDate).getTime();
|
||||
const diffMs = now - then;
|
||||
const diffSec = Math.floor(diffMs / 1000);
|
||||
const diffMin = Math.floor(diffSec / 60);
|
||||
const diffHr = Math.floor(diffMin / 60);
|
||||
const diffDay = Math.floor(diffHr / 24);
|
||||
|
||||
if (diffSec < 60) return "just now";
|
||||
if (diffMin < 60) return `${diffMin} min ago`;
|
||||
if (diffHr < 24) return `${diffHr}h ago`;
|
||||
return `${diffDay}d ago`;
|
||||
}
|
||||
|
||||
interface MessageCardProps {
|
||||
agentName: string;
|
||||
agentStatus: string;
|
||||
preview: string;
|
||||
timestamp: string;
|
||||
requiresResponse: boolean;
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
function formatStatusLabel(status: string): string {
|
||||
return status.replace(/_/g, " ");
|
||||
}
|
||||
|
||||
function truncatePreview(text: string, maxLength = 80): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.slice(0, maxLength) + "...";
|
||||
}
|
||||
|
||||
export function MessageCard({
|
||||
agentName,
|
||||
agentStatus,
|
||||
preview,
|
||||
timestamp,
|
||||
requiresResponse,
|
||||
isSelected,
|
||||
onClick,
|
||||
}: MessageCardProps) {
|
||||
return (
|
||||
<Card
|
||||
className={cn(
|
||||
"cursor-pointer p-4 transition-colors hover:bg-accent/50",
|
||||
isSelected && "bg-accent",
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
requiresResponse ? "text-orange-500" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{requiresResponse ? "\u25CF" : "\u25CB"}
|
||||
</span>
|
||||
<span className="text-sm font-bold">
|
||||
{agentName}{" "}
|
||||
<span className="font-normal text-muted-foreground">
|
||||
({formatStatusLabel(agentStatus)})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 pl-5 text-sm text-muted-foreground">
|
||||
“{truncatePreview(preview)}”
|
||||
</p>
|
||||
</div>
|
||||
<span className="shrink-0 text-xs text-muted-foreground">
|
||||
{formatRelativeTime(timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user