Ship growth intelligence and demo polish
CI / verify (push) Successful in 3m51s

This commit is contained in:
2026-08-13 04:44:14 -07:00
parent a6167629cc
commit 1318c0b841
14 changed files with 112 additions and 35 deletions
+2
View File
@@ -26,6 +26,7 @@ const FactReview = lazy(() => import('@/pages/FactReview').then(({ FactReview })
const Contracts = lazy(() => import('@/pages/Contracts').then(({ Contracts }) => ({ default: Contracts })));
const Imports = lazy(() => import('@/pages/Imports').then(({ Imports }) => ({ default: Imports })));
const Piggy = lazy(() => import('@/pages/Piggy').then(({ Piggy }) => ({ default: Piggy })));
const Growth = lazy(() => import('@/pages/Growth').then(({ Growth }) => ({ default: Growth })));
const queryClient = new QueryClient({
defaultOptions: {
@@ -168,6 +169,7 @@ function AuthGate({ config }: { config: PublicConfig }) {
<Route element={<Shell />}>
<Route index element={<RoutePage><Overview /></RoutePage>} />
<Route path="margin" element={<RoutePage><Margin /></RoutePage>} />
<Route path="growth" element={<RoutePage><Growth /></RoutePage>} />
<Route path="capacity" element={<RoutePage><Capacity /></RoutePage>} />
<Route path="demand" element={<RoutePage><DemandPipeline /></RoutePage>} />
<Route path="supply" element={<RoutePage><SupplyPipeline /></RoutePage>} />
+4 -2
View File
@@ -26,6 +26,7 @@ import {
ShieldCheck,
Settings,
TrendingUp,
Target,
Users,
} from 'lucide-react';
import { PiggyLogo, PiggyMark } from './PiggyMark';
@@ -39,6 +40,7 @@ interface NavItem extends CommandDestination {
const NAV: NavItem[] = [
{ to: '/', label: 'Overview', icon: LayoutDashboard, primary: true },
{ to: '/growth', label: 'Growth', icon: Target },
{ to: '/piggy', label: 'Piggy', icon: MessageCircleMore },
{ to: '/margin', label: 'Margin', icon: TrendingUp, primary: true },
{ to: '/capacity', label: 'Capacity', icon: Server, primary: true },
@@ -90,7 +92,7 @@ export function Shell() {
end={item.to === '/'}
className={({ isActive }) =>
cn(
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
'flex min-h-[44px] items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
isActive
? 'bg-accent-subtle text-accent-fg'
: 'text-muted hover:bg-surface-2 hover:text-fg',
@@ -106,7 +108,7 @@ export function Shell() {
type="button"
variant="ghost"
size="sm"
className="mx-3 mb-3 justify-start text-muted"
className="mx-3 mb-3 min-h-[44px] justify-start text-muted"
onClick={() => setCommandOpen(true)}
>
<Search className="h-4 w-4" aria-hidden />
+1 -1
View File
@@ -42,7 +42,7 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<DialogPrimitive.Close className="absolute right-1 top-1 flex size-[44px] items-center justify-center rounded-md opacity-70 ring-offset-background transition-opacity hover:bg-accent hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
+6 -2
View File
@@ -11,15 +11,19 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
"group peer relative inline-flex size-[44px] shrink-0 cursor-pointer items-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
ref={ref}
>
<span
aria-hidden
className="absolute left-1 top-3 h-5 w-9 rounded-full border-2 border-transparent bg-input shadow-sm transition-colors group-data-[state=checked]:bg-primary"
/>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
"pointer-events-none absolute left-1.5 top-3.5 block size-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
+20 -7
View File
@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { cloneElement, isValidElement, useId, useMemo, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
AlertTriangle,
@@ -364,13 +364,13 @@ export function Contracts() {
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
<EnumSelect value={type} onValueChange={(value) => setType(value as typeof type)}>
<EnumSelect aria-label="Filter by paper type" value={type} onValueChange={(value) => setType(value as typeof type)}>
<SelectItem value="all">All paper</SelectItem>
{CONTRACT_TYPES.map((value) => (
<SelectItem key={value} value={value}>{TYPE_LABELS[value]}</SelectItem>
))}
</EnumSelect>
<EnumSelect value={side} onValueChange={(value) => setSide(value as typeof side)}>
<EnumSelect aria-label="Filter by market side" value={side} onValueChange={(value) => setSide(value as typeof side)}>
<SelectItem value="all">Both sides</SelectItem>
<SelectItem value="demand">Demand</SelectItem>
<SelectItem value="supply">Supply</SelectItem>
@@ -958,16 +958,29 @@ function formFromDetail(detail: ContractDetail): ContractFormState {
};
}
function EnumSelect({ children, ...props }: React.ComponentProps<typeof Select>) {
return <Select {...props}><SelectTrigger className="h-11"><SelectValue /></SelectTrigger><SelectContent><SelectGroup>{children}</SelectGroup></SelectContent></Select>;
type EnumSelectProps = React.ComponentProps<typeof Select> & {
id?: string;
'aria-label'?: string;
};
function EnumSelect({ children, id, 'aria-label': ariaLabel, ...props }: EnumSelectProps) {
return <Select {...props}><SelectTrigger id={id} aria-label={ariaLabel} className="h-11"><SelectValue /></SelectTrigger><SelectContent><SelectGroup>{children}</SelectGroup></SelectContent></Select>;
}
function Field({ label, hint, className, children }: { label: string; hint?: string; className?: string; children: React.ReactNode }) {
return <div className={cn('flex flex-col gap-1.5', className)}><Label>{label}</Label>{children}{hint ? <p className="text-xs text-muted">{hint}</p> : null}</div>;
const id = useId();
const control = isValidElement<{ id?: string; 'aria-label'?: string }>(children)
? cloneElement(children, {
id: children.props.id ?? id,
'aria-label': children.props['aria-label'] ?? label,
})
: children;
return <div className={cn('flex flex-col gap-1.5', className)}><Label htmlFor={id}>{label}</Label>{control}{hint ? <p className="text-xs text-muted">{hint}</p> : null}</div>;
}
function ToggleField({ label, checked, onCheckedChange }: { label: string; checked: boolean; onCheckedChange(value: boolean): void }) {
return <div className="flex min-h-11 items-center justify-between gap-3 rounded-lg border border-border px-3"><Label>{label}</Label><Switch checked={checked} onCheckedChange={onCheckedChange} /></div>;
const id = useId();
return <div className="flex min-h-11 items-center justify-between gap-3 rounded-lg border border-border px-3"><Label htmlFor={id}>{label}</Label><Switch id={id} checked={checked} onCheckedChange={onCheckedChange} /></div>;
}
function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
+7
View File
@@ -31,6 +31,7 @@ interface GrowthReport {
rulesetVersion: string;
computedAt: string;
customers: GrowthCustomer[];
customersTruncated: boolean;
idleSupply: Array<{
commitmentId: string;
name: string;
@@ -84,6 +85,12 @@ export function Growth() {
</div>
</header>
{data.customersTruncated ? (
<div className="rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm text-warning" role="status">
Showing the 200 most recently updated demand accounts. Narrow the workspace before treating these totals as complete.
</div>
) : null}
<section className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
<Stat label="Deployed customers" value={deployed} hint="Active sold capacity" />
<Stat label="Expansion candidates" value={expansion} hint="Evidence-backed openings" tone={expansion ? 'positive' : 'default'} />