mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 10:13:11 +02:00
57 lines
3.2 KiB
TypeScript
57 lines
3.2 KiB
TypeScript
import type { CostByUserReport } from "@paperclipai/shared";
|
|
import { CostEstimateLabel } from "./CostEstimateLabel";
|
|
import { Identity } from "./Identity";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
|
import { formatCents, formatTokens } from "../lib/utils";
|
|
|
|
export function CostByUserTable({ report }: { report: CostByUserReport }) {
|
|
return (
|
|
<Card className="gap-3 py-4">
|
|
<CardHeader className="gap-0 px-5">
|
|
<CardTitle className="text-base">By user</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="px-5">
|
|
<div className="overflow-x-auto">
|
|
<table aria-label="Costs by user" className="w-full text-sm">
|
|
<thead className="text-muted-foreground">
|
|
<tr className="border-b border-border">
|
|
<th scope="col" className="py-2 pr-4 text-left font-medium">User</th>
|
|
<th scope="col" className="px-4 py-2 text-right font-medium">Runs</th>
|
|
<th scope="col" className="px-4 py-2 text-right font-medium">Input</th>
|
|
<th scope="col" className="px-4 py-2 text-right font-medium">Output</th>
|
|
<th scope="col" className="py-2 pl-4 text-right font-medium">Cost</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{report.rows.length === 0 && (
|
|
<tr><td colSpan={5} className="py-3 text-muted-foreground">No user-attributed costs yet.</td></tr>
|
|
)}
|
|
{report.rows.map(row => (
|
|
<tr key={row.userId ?? "unattributed"} className="border-b border-border last:border-0">
|
|
<th scope="row" className="py-3 pr-4 text-left font-normal">
|
|
{row.userId ? <Identity name={row.userName ?? "Unknown user"} avatarUrl={row.userImage} /> : "Unattributed"}
|
|
</th>
|
|
<td className="px-4 py-3 text-right tabular-nums">{row.runCount.toLocaleString()}</td>
|
|
<td className="px-4 py-3 text-right tabular-nums">
|
|
{formatTokens(row.inputTokens + row.cachedInputTokens)}
|
|
{row.cachedInputTokens > 0 && <div className="whitespace-nowrap text-xs text-muted-foreground">{formatTokens(row.cachedInputTokens)} cached</div>}
|
|
</td>
|
|
<td className="px-4 py-3 text-right tabular-nums">{formatTokens(row.outputTokens)}</td>
|
|
<td className="py-3 pl-4 text-right tabular-nums">
|
|
<div>{row.costCents === 0 && row.eventCount > 0 && row.unpricedEventCount === row.eventCount ? "—" : formatCents(row.costCents)}</div>
|
|
<CostEstimateLabel eventCount={row.eventCount} estimatedEventCount={row.estimatedEventCount} />
|
|
{row.unpricedEventCount > 0 && <div className="whitespace-nowrap text-xs text-muted-foreground">{row.unpricedEventCount} unpriced {row.unpricedEventCount === 1 ? "charge" : "charges"}</div>}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{report.rows.some(row => row.userId === null) && (
|
|
<p className="mt-3 text-xs text-muted-foreground">Unattributed costs have no recorded user in this organization.</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|