Add a time tooltip in the task card (#66)

This commit is contained in:
Matt Rubens 2025-06-02 11:27:48 -04:00 committed by GitHub
parent a60938cea3
commit 76d50abbf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 111 additions and 59 deletions

View file

@ -15,3 +15,4 @@ export * from './sonner';
export * from './switch';
export * from './table';
export * from './tabs';
export * from './tooltip';

View file

@ -0,0 +1,30 @@
'use client';
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View file

@ -1,6 +1,18 @@
import { formatNumber, formatCurrency } from '@/lib/formatters';
import {
formatNumber,
formatCurrency,
formatTimestamp,
} from '@/lib/formatters';
import { generateFallbackTitle } from '@/lib/taskUtils';
import { Card, CardContent, Button } from '@/components/ui';
import {
Card,
CardContent,
Button,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui';
import type { TaskWithUser } from '@/actions/analytics';
import type { Filter } from '@/app/(authenticated)/usage/types';
@ -35,66 +47,75 @@ const formatElegantTime = (timestamp: number): string => {
export const TaskCard = ({ task, onFilter, onTaskSelected }: TaskCardProps) => {
return (
<Card
className="cursor-pointer hover:shadow-md transition-shadow py-0"
onClick={() => onTaskSelected(task)}
>
<CardContent className="p-0">
<div className="px-4 py-2.5">
{/* Line 1: Title and Status */}
<div className="flex items-center justify-between gap-2 mb-1.5">
<h3 className="font-medium text-sm truncate flex-1 leading-none">
{task.title || generateFallbackTitle(task)}
</h3>
<Status completed={task.completed} />
</div>
{/* Line 2: All metadata in a single line with bullet separators between items */}
<div className="flex items-center justify-between text-xs text-muted-foreground">
{/* Left side: Date, User, and Mode (if present) with bullet separators */}
<div className="flex items-center">
<span className="whitespace-nowrap">
{formatElegantTime(task.timestamp)}
</span>
<div className="mx-2 text-muted-foreground/30"></div>
<Button
variant="link"
size="sm"
onClick={(e) => {
e.stopPropagation();
onFilter({
type: 'userId',
value: task.userId,
label: task.user.name,
});
}}
className="px-0 h-auto text-xs font-normal text-muted-foreground hover:text-foreground"
>
{task.user.name}
</Button>
{/* Mode (if present) */}
{task.mode && (
<>
<div className="mx-2 text-muted-foreground/30"></div>
<span className="text-xs font-medium text-muted-foreground">
{task.mode}
</span>
</>
)}
<TooltipProvider>
<Card
className="cursor-pointer hover:shadow-md transition-shadow py-0"
onClick={() => onTaskSelected(task)}
>
<CardContent className="p-0">
<div className="px-4 py-2.5">
{/* Line 1: Title and Status */}
<div className="flex items-center justify-between gap-2 mb-1.5">
<h3 className="font-medium text-sm truncate flex-1 leading-none">
{task.title || generateFallbackTitle(task)}
</h3>
<Status completed={task.completed} />
</div>
{/* Right side: Tokens and Cost with bullet separator */}
<div className="flex items-center whitespace-nowrap">
<span className="font-medium">
{formatNumber(task.tokens)} tokens
</span>
<div className="mx-2 text-muted-foreground/30"></div>
<span className="font-medium">{formatCurrency(task.cost)}</span>
{/* Line 2: All metadata in a single line with bullet separators between items */}
<div className="flex items-center justify-between text-xs text-muted-foreground">
{/* Left side: Date, User, and Mode (if present) with bullet separators */}
<div className="flex items-center">
<Tooltip>
<TooltipTrigger asChild>
<span className="whitespace-nowrap">
{formatElegantTime(task.timestamp)}
</span>
</TooltipTrigger>
<TooltipContent>
<p>{formatTimestamp(task.timestamp)}</p>
</TooltipContent>
</Tooltip>
<div className="mx-2 text-muted-foreground/30"></div>
<Button
variant="link"
size="sm"
onClick={(e) => {
e.stopPropagation();
onFilter({
type: 'userId',
value: task.userId,
label: task.user.name,
});
}}
className="px-0 h-auto text-xs font-normal text-muted-foreground hover:text-foreground"
>
{task.user.name}
</Button>
{/* Mode (if present) */}
{task.mode && (
<>
<div className="mx-2 text-muted-foreground/30"></div>
<span className="text-xs font-medium text-muted-foreground">
{task.mode}
</span>
</>
)}
</div>
{/* Right side: Tokens and Cost with bullet separator */}
<div className="flex items-center whitespace-nowrap">
<span className="font-medium">
{formatNumber(task.tokens)} tokens
</span>
<div className="mx-2 text-muted-foreground/30"></div>
<span className="font-medium">{formatCurrency(task.cost)}</span>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</CardContent>
</Card>
</TooltipProvider>
);
};