Reframe each screen around the decisions compute brokers make: sellable capacity, full-cost margin, pipeline movement, contract deadlines, evidence review, staged imports, and controlled agent access. Group the shell by operating domain, strengthen mobile navigation and sheets, add responsive record treatments, and make loading, error, empty, readiness, and retry states explicit. The visual audit exposed sortable table targets and an unnamed file input only after exercising the rendered app, so this commit also pins those accessibility decisions at their actual interaction boundaries. Manrope is self-hosted as a single Latin variable subset to keep the stronger hierarchy without shipping unused font payloads.
This commit is contained in:
@@ -6,8 +6,10 @@
|
||||
* is the entire value of this view.
|
||||
*/
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { compactNumber, get, money, moneyExact, percent } from '@/lib/api';
|
||||
import { Card, CardContent, CardHeader, CardTitle, EmptyState, Skeleton, Stat } from '@/components/ui';
|
||||
import { Button, Card, CardContent, CardHeader, CardTitle, EmptyState, Skeleton, Stat } from '@/components/ui';
|
||||
import { usePageTitle } from '@/lib/title';
|
||||
|
||||
interface MarginReport {
|
||||
@@ -38,13 +40,25 @@ interface MarginReport {
|
||||
|
||||
export function Margin() {
|
||||
usePageTitle('Margin');
|
||||
const { data, isLoading } = useQuery({
|
||||
const { data, isLoading, error, refetch } = useQuery({
|
||||
queryKey: ['margin'],
|
||||
queryFn: () => get<MarginReport>('/api/capacity/margin'),
|
||||
});
|
||||
|
||||
if (isLoading) return <Skeleton className="h-96" />;
|
||||
if (!data || data.blocks.length === 0) {
|
||||
if (isLoading) {
|
||||
return <div className="flex flex-col gap-4"><Skeleton className="h-16" /><div className="grid grid-cols-2 gap-2 xl:grid-cols-4">{Array.from({ length: 4 }).map((_, index) => <Skeleton key={index} className="h-28" />)}</div><Skeleton className="h-80" /></div>;
|
||||
}
|
||||
if (error || !data) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center gap-4 pt-6">
|
||||
<EmptyState title="Could not load margin" description={error instanceof Error ? error.message : 'The margin ledger is unavailable.'} />
|
||||
<Button variant="outline" onClick={() => void refetch()}>Try again</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
if (data.blocks.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="No capacity to report on"
|
||||
@@ -56,7 +70,7 @@ export function Margin() {
|
||||
const t = data.totals;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-5 pb-[calc(5.5rem+var(--safe-bottom))] md:pb-0">
|
||||
<header>
|
||||
<h1 className="text-xl font-semibold tracking-tight sm:text-2xl">Margin</h1>
|
||||
<p className="mt-1 max-w-2xl text-sm text-muted">
|
||||
@@ -64,7 +78,7 @@ export function Margin() {
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<section className="grid grid-cols-2 gap-2 sm:gap-3 xl:grid-cols-4">
|
||||
<Stat label="Revenue" value={money(t.revenueCents)} />
|
||||
<Stat label="Cost" value={money(t.costCents)} hint="Full commitment" />
|
||||
<Stat
|
||||
@@ -76,23 +90,29 @@ export function Margin() {
|
||||
<Stat
|
||||
label="Per sold GPU-hour"
|
||||
value={moneyExact(t.marginPerAllocatedGpuHourCents)}
|
||||
hint={`${percent(t.utilisation, 1)} utilised`}
|
||||
hint={`${percent(t.utilisation, 1)} of committed hours sold`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">By commitment</CardTitle>
|
||||
<CardHeader className="flex-row items-center justify-between gap-3 space-y-0">
|
||||
<div>
|
||||
<CardTitle className="text-base">By commitment</CardTitle>
|
||||
<p className="mt-1 text-xs text-muted">Sold ratio describes contracted capacity sold, not workload utilization.</p>
|
||||
</div>
|
||||
<Link to="/capacity" className="tap inline-flex min-h-11 shrink-0 items-center gap-1 rounded-lg px-3 text-sm font-medium text-accent-fg hover:bg-surface-2">
|
||||
Capacity <ArrowRight className="size-4" aria-hidden />
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent className="px-0 sm:px-0">
|
||||
<div className="scroll-x">
|
||||
<div className="hidden scroll-x md:block">
|
||||
<table className="w-full min-w-[720px] text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-left text-xs uppercase tracking-wide text-muted">
|
||||
<th className="px-4 pb-2 font-medium sm:px-5">Commitment</th>
|
||||
<th className="px-4 pb-2 text-right font-medium">Sold</th>
|
||||
<th className="px-4 pb-2 text-right font-medium">Free</th>
|
||||
<th className="px-4 pb-2 text-right font-medium">Utilisation</th>
|
||||
<th className="px-4 pb-2 text-right font-medium">Sellable</th>
|
||||
<th className="px-4 pb-2 text-right font-medium">Sold ratio</th>
|
||||
<th className="px-4 pb-2 text-right font-medium">Cost/hr</th>
|
||||
<th className="px-4 pb-2 text-right font-medium sm:px-5">Break even</th>
|
||||
</tr>
|
||||
@@ -129,22 +149,39 @@ export function Margin() {
|
||||
is technically true and reads like a bug — the same fix
|
||||
already applied on the capacity cards.
|
||||
*/}
|
||||
<td className="px-4 py-3 text-right sm:px-5">
|
||||
{block.breakEvenPriceCents == null ? (
|
||||
<span className="text-muted">Sold out</span>
|
||||
) : block.breakEvenPriceCents === 0 ? (
|
||||
<span className="text-positive">Covered</span>
|
||||
) : (
|
||||
<span className="nums">{moneyExact(block.breakEvenPriceCents)}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right sm:px-5"><BreakEven value={block.breakEvenPriceCents} /></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="grid gap-3 px-4 pb-4 md:hidden">
|
||||
{data.blocks.map((block) => (
|
||||
<article key={block.commitmentId} className="rounded-xl border border-border p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h3 className="break-words font-medium leading-snug">{block.name}</h3>
|
||||
<p className="mt-1 text-xs text-muted">{block.gpuCount}× {block.gpuType}</p>
|
||||
</div>
|
||||
<span className={['nums shrink-0 text-sm font-semibold', block.utilisation < 0.5 ? 'text-warning' : ''].join(' ')}>{percent(block.utilisation)} sold</span>
|
||||
</div>
|
||||
<dl className="mt-4 grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
|
||||
<dt className="text-muted">Sold capacity</dt><dd className="nums text-right">{compactNumber(block.soldGpuHours)} hrs</dd>
|
||||
<dt className="text-muted">Sellable capacity</dt><dd className="nums text-right">{compactNumber(block.availableGpuHours)} hrs</dd>
|
||||
<dt className="text-muted">Our cost</dt><dd className="nums text-right">{moneyExact(block.costPerGpuHourCents)}/GPU-hr</dd>
|
||||
<dt className="text-muted">Break even</dt><dd className="text-right"><BreakEven value={block.breakEvenPriceCents} /></dd>
|
||||
</dl>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BreakEven({ value }: { value: number | null }) {
|
||||
if (value == null) return <span className="text-muted">Sold out</span>;
|
||||
if (value === 0) return <span className="text-positive">Cost covered</span>;
|
||||
return <span className="nums">{moneyExact(value)}/GPU-hr</span>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user