"use client"; import { useQuery } from "@tanstack/react-query"; import { cockpitApi, type CockpitSummary } from "@/lib/api/cockpit"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { OfflineState } from "@/components/ui/offline-state"; import { HelpTip } from "@/components/ui/help-tip"; import { useUsageTimeSeries } from "@/hooks/use-usage"; import { SpendTrendChart } from "./spend-trend-chart"; import type { UsageTimePoint } from "@/types"; // --------------------------------------------------------------------------- // Loading skeleton — three grouped skeleton blocks // --------------------------------------------------------------------------- function ScorecardSkeleton() { return ( {/* Group 1: Delivery + Spend */}
{/* Group 2: Spend */}
{/* Group 3: Speed + Objectives */}
); } // --------------------------------------------------------------------------- // Section header helper // --------------------------------------------------------------------------- function SectionLabel({ children, ...props }: React.ComponentProps<"p">) { return (

{children}

); } // --------------------------------------------------------------------------- // Delivery section // --------------------------------------------------------------------------- interface DeliveryMetricProps { label: string; value: number; hint: string; } function DeliveryMetric({ label, value, hint }: DeliveryMetricProps) { return (
{value}
{label}
); } interface DeliverySectionProps { delivery: CockpitSummary["delivery"]; } function DeliverySection({ delivery }: DeliverySectionProps) { return (
Delivery
); } // --------------------------------------------------------------------------- // Spend section // --------------------------------------------------------------------------- interface SpendSectionProps { spend: CockpitSummary["spend"]; spendTrend: UsageTimePoint[] | undefined; spendTrendLoading: boolean; } function SpendSection({ spend, spendTrend, spendTrendLoading, }: SpendSectionProps) { const { monthly_budget_cap_usd, spend_30d_usd, projected_monthly_usd, over_budget, } = spend; // Red/destructive only when cap is a non-null number AND over_budget is true const isOverBudget = monthly_budget_cap_usd !== null && over_budget; return (
Spend
30-day spend ${spend_30d_usd.toFixed(2)}
{projected_monthly_usd !== null && (
Projected monthly ${projected_monthly_usd.toFixed(2)}
)}
Monthly cap {monthly_budget_cap_usd === null ? ( No budget cap set ) : ( ${monthly_budget_cap_usd.toFixed(2)} {isOverBudget && ( (over budget) )} )}
); } // --------------------------------------------------------------------------- // Speed section // --------------------------------------------------------------------------- interface SpeedSectionProps { medianLeadTimeHours: number | null | undefined; } function SpeedSection({ medianLeadTimeHours }: SpeedSectionProps) { // Show 'No data yet' when null or undefined. Never render '0h'. const hasData = medianLeadTimeHours != null; return (
Speed
Median lead time {hasData ? ( {medianLeadTimeHours.toFixed(1)}h median — target: < 24h ) : ( No data yet )}
); } // --------------------------------------------------------------------------- // Objectives section — three charter objective cards, each showing a live // metric against its target. // // ponytail: The charter `objectives` field is free text // (Record[]) while the three metrics are hardcoded // (first_pass_yield, median_lead_time_hours, escaped_defects). The mapping // is positional-by-convention, not derived — one card per charter objective // only holds until you edit the Goals tab. Stated assumption, not // discovered later. // --------------------------------------------------------------------------- const OBJECTIVE_FALLBACK_LABELS = [ "Tasks shipped to merge with no human code edits", "Median lead time, intake → merged", "Critical escaped defects per release", ] as const; interface ObjectivesSectionProps { objectives: Record[]; firstPassYield: number | null | undefined; medianLeadTimeHours: number | null | undefined; escapedDefects: number | null | undefined; } function objectiveLabel( objectives: Record[], index: number, ): string { const raw = objectives[index]?.metric; return typeof raw === "string" && raw.length > 0 ? raw : OBJECTIVE_FALLBACK_LABELS[index]; } interface ObjectiveCardProps { label: string; hasData: boolean; formattedValue: string; targetText: string; } function ObjectiveCard({ label, hasData, formattedValue, targetText, }: ObjectiveCardProps) { return (
{label}
{hasData ? ( {formattedValue} ) : ( No data yet )} target: {targetText}
); } function ObjectivesSection({ objectives, firstPassYield, medianLeadTimeHours, escapedDefects, }: ObjectivesSectionProps) { // Show 'No data yet' when a metric is null or undefined — never a fabricated // value. Mirrors SpeedSection's hasData guard. const fpyHasData = firstPassYield != null; const ltHasData = medianLeadTimeHours != null; const edHasData = escapedDefects != null; return (
Objectives
); } // --------------------------------------------------------------------------- // Scorecard body — rendered when data is available // --------------------------------------------------------------------------- interface ScorecardBodyProps { data: CockpitSummary; spendTrend: UsageTimePoint[] | undefined; spendTrendLoading: boolean; } function ScorecardBody({ data, spendTrend, spendTrendLoading, }: ScorecardBodyProps) { return ( Company Scorecard Live performance against the charter ); } // --------------------------------------------------------------------------- // Public export // --------------------------------------------------------------------------- export function CompanyScorecardCard() { const { data, isLoading, isError, refetch } = useQuery({ queryKey: ["cockpit-summary"], queryFn: cockpitApi.summary, }); const { data: spendTrend, isLoading: spendTrendLoading } = useUsageTimeSeries("30d"); if (isLoading) return ; if (isError || !data) { return ( void refetch()} /> ); } return ( ); }