2026-03-08 19:18:04 +05:30
import { useEffect , useMemo , useRef , useState } from "react" ;
2026-02-17 12:24:48 -06:00
import { useQuery } from "@tanstack/react-query" ;
2026-03-08 20:56:13 +05:30
import type { CostByAgentModel , CostByProviderModel , CostWindowSpendRow , QuotaWindow } from "@paperclipai/shared" ;
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
import { costsApi } from "../api/costs" ;
import { useCompany } from "../context/CompanyContext" ;
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
import { useBreadcrumbs } from "../context/BreadcrumbContext" ;
2026-02-17 12:24:48 -06:00
import { queryKeys } from "../lib/queryKeys" ;
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
import { EmptyState } from "../components/EmptyState" ;
2026-03-02 16:44:03 -06:00
import { PageSkeleton } from "../components/PageSkeleton" ;
2026-03-08 17:11:08 +05:30
import { ProviderQuotaCard } from "../components/ProviderQuotaCard" ;
import { PageTabBar } from "../components/PageTabBar" ;
import { formatCents , formatTokens , providerDisplayName } from "../lib/utils" ;
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
import { Identity } from "../components/Identity" ;
import { StatusBadge } from "../components/StatusBadge" ;
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
import { Card , CardContent } from "@/components/ui/card" ;
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
import { Button } from "@/components/ui/button" ;
2026-03-08 17:11:08 +05:30
import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
2026-03-08 20:56:13 +05:30
import { DollarSign , ChevronDown , ChevronRight } from "lucide-react" ;
2026-03-08 17:11:08 +05:30
import { useDateRange , PRESET_LABELS , PRESET_KEYS } from "../hooks/useDateRange" ;
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
2026-03-08 19:04:27 +05:30
// sentinel used in query keys when no company is selected, to avoid polluting the cache
// with undefined/null entries before the early-return guard fires
const NO_COMPANY = "__none__" ;
2026-03-08 17:11:08 +05:30
// ---------- helpers ----------
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
2026-03-08 17:11:08 +05:30
/** current week mon-sun boundaries as iso strings */
function currentWeekRange ( ) : { from : string ; to : string } {
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
const now = new Date ( ) ;
2026-03-08 17:11:08 +05:30
const day = now . getDay ( ) ; // 0 = Sun
const diffToMon = day === 0 ? - 6 : 1 - day ;
const mon = new Date ( now . getFullYear ( ) , now . getMonth ( ) , now . getDate ( ) + diffToMon , 0 , 0 , 0 , 0 ) ;
2026-03-08 19:04:27 +05:30
const sun = new Date ( mon . getFullYear ( ) , mon . getMonth ( ) , mon . getDate ( ) + 6 , 23 , 59 , 59 , 999 ) ;
2026-03-08 17:11:08 +05:30
return { from : mon . toISOString ( ) , to : sun.toISOString ( ) } ;
}
function ProviderTabLabel ( { provider , rows } : { provider : string ; rows : CostByProviderModel [ ] } ) {
const totalTokens = rows . reduce ( ( s , r ) = > s + r . inputTokens + r . outputTokens , 0 ) ;
const totalCost = rows . reduce ( ( s , r ) = > s + r . costCents , 0 ) ;
return (
< span className = "flex items-center gap-1.5" >
< span > { providerDisplayName ( provider ) } < / span >
< span className = "text-xs text-muted-foreground font-mono" > { formatTokens ( totalTokens ) } < / span >
< span className = "text-xs text-muted-foreground" > { formatCents ( totalCost ) } < / span >
< / span >
) ;
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
}
2026-03-08 17:11:08 +05:30
// ---------- page ----------
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
export function Costs() {
const { selectedCompanyId } = useCompany ( ) ;
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
const { setBreadcrumbs } = useBreadcrumbs ( ) ;
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
2026-03-08 17:11:08 +05:30
const [ mainTab , setMainTab ] = useState < "spend" | "providers" > ( "spend" ) ;
const [ activeProvider , setActiveProvider ] = useState ( "all" ) ;
const {
preset ,
setPreset ,
customFrom ,
setCustomFrom ,
customTo ,
setCustomTo ,
from ,
to ,
customReady ,
} = useDateRange ( ) ;
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
useEffect ( ( ) = > {
setBreadcrumbs ( [ { label : "Costs" } ] ) ;
} , [ setBreadcrumbs ] ) ;
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
2026-03-08 19:18:04 +05:30
// today as state so the weekRange memo refreshes after midnight.
// stable [] dep + ref avoids the StrictMode double-invoke problem of the
// chained [today] dep pattern (which would schedule two concurrent timers).
2026-03-08 19:04:27 +05:30
const [ today , setToday ] = useState ( ( ) = > new Date ( ) . toDateString ( ) ) ;
2026-03-08 19:18:04 +05:30
const todayTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
2026-03-08 19:04:27 +05:30
useEffect ( ( ) = > {
2026-03-08 19:18:04 +05:30
const schedule = ( ) = > {
2026-03-08 19:04:27 +05:30
const now = new Date ( ) ;
2026-03-08 19:18:04 +05:30
const ms = new Date ( now . getFullYear ( ) , now . getMonth ( ) , now . getDate ( ) + 1 ) . getTime ( ) - now . getTime ( ) ;
todayTimerRef . current = setTimeout ( ( ) = > {
setToday ( new Date ( ) . toDateString ( ) ) ;
schedule ( ) ;
} , ms ) ;
2026-03-08 19:04:27 +05:30
} ;
2026-03-08 19:18:04 +05:30
schedule ( ) ;
return ( ) = > { if ( todayTimerRef . current != null ) clearTimeout ( todayTimerRef . current ) ; } ;
} , [ ] ) ;
2026-03-08 19:04:27 +05:30
const weekRange = useMemo ( ( ) = > currentWeekRange ( ) , [ today ] ) ;
2026-03-08 17:11:08 +05:30
// ---------- spend tab queries (no polling — cost data doesn't change in real time) ----------
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
2026-03-08 19:04:27 +05:30
const companyId = selectedCompanyId ? ? NO_COMPANY ;
2026-03-08 17:11:08 +05:30
const { data : spendData , isLoading : spendLoading , error : spendError } = useQuery ( {
2026-03-08 19:04:27 +05:30
queryKey : queryKeys.costs ( companyId , from || undefined , to || undefined ) ,
2026-02-17 12:24:48 -06:00
queryFn : async ( ) = > {
2026-03-08 20:56:13 +05:30
const [ summary , byAgent , byProject , byAgentModel ] = await Promise . all ( [
2026-03-08 19:04:27 +05:30
costsApi . summary ( companyId , from || undefined , to || undefined ) ,
costsApi . byAgent ( companyId , from || undefined , to || undefined ) ,
costsApi . byProject ( companyId , from || undefined , to || undefined ) ,
2026-03-08 20:56:13 +05:30
costsApi . byAgentModel ( companyId , from || undefined , to || undefined ) ,
2026-02-17 12:24:48 -06:00
] ) ;
2026-03-08 20:56:13 +05:30
return { summary , byAgent , byProject , byAgentModel } ;
2026-02-17 12:24:48 -06:00
} ,
2026-03-08 17:11:08 +05:30
enabled : ! ! selectedCompanyId && customReady ,
} ) ;
2026-03-08 20:56:13 +05:30
// tracks which agent rows are expanded in the By Agent card.
// reset whenever the date range or company changes so stale open-states
// from a previous query window don't bleed into the new result set.
const [ expandedAgents , setExpandedAgents ] = useState < Set < string > > ( new Set ( ) ) ;
useEffect ( ( ) = > {
setExpandedAgents ( new Set ( ) ) ;
} , [ companyId , from , to ] ) ;
function toggleAgent ( agentId : string ) {
setExpandedAgents ( ( prev ) = > {
const next = new Set ( prev ) ;
if ( next . has ( agentId ) ) next . delete ( agentId ) ;
else next . add ( agentId ) ;
return next ;
} ) ;
}
// group byAgentModel rows by agentId for O(1) lookup in the render pass.
// sub-rows are sorted by cost descending so the most expensive model is first.
const agentModelRows = useMemo ( ( ) = > {
const map = new Map < string , CostByAgentModel [ ] > ( ) ;
for ( const row of spendData ? . byAgentModel ? ? [ ] ) {
const arr = map . get ( row . agentId ) ? ? [ ] ;
arr . push ( row ) ;
map . set ( row . agentId , arr ) ;
}
for ( const [ id , rows ] of map ) {
map . set ( id , rows . slice ( ) . sort ( ( a , b ) = > b . costCents - a . costCents ) ) ;
}
return map ;
} , [ spendData ? . byAgentModel ] ) ;
2026-03-08 17:11:08 +05:30
// ---------- providers tab queries (polling — provider quota changes during agent runs) ----------
const { data : providerData } = useQuery ( {
2026-03-08 19:04:27 +05:30
queryKey : queryKeys.usageByProvider ( companyId , from || undefined , to || undefined ) ,
queryFn : ( ) = > costsApi . byProvider ( companyId , from || undefined , to || undefined ) ,
2026-03-08 20:56:13 +05:30
enabled : ! ! selectedCompanyId && customReady && mainTab === "providers" ,
2026-03-08 17:11:08 +05:30
refetchInterval : 30_000 ,
staleTime : 10_000 ,
} ) ;
2026-03-08 21:39:19 +05:30
// weekData intentionally omits the customReady guard — it always uses the
// fixed current-week range (weekRange), not the user's custom date selection.
// running it unconditionally (when the providers tab is active) ensures the
// week-over-week spend column is always populated on tab mount.
2026-03-08 17:11:08 +05:30
const { data : weekData } = useQuery ( {
2026-03-08 19:04:27 +05:30
queryKey : queryKeys.usageByProvider ( companyId , weekRange . from , weekRange . to ) ,
queryFn : ( ) = > costsApi . byProvider ( companyId , weekRange . from , weekRange . to ) ,
2026-03-08 20:56:13 +05:30
enabled : ! ! selectedCompanyId && mainTab === "providers" ,
2026-03-08 17:11:08 +05:30
refetchInterval : 30_000 ,
staleTime : 10_000 ,
} ) ;
const { data : windowData } = useQuery ( {
2026-03-08 19:04:27 +05:30
queryKey : queryKeys.usageWindowSpend ( companyId ) ,
queryFn : ( ) = > costsApi . windowSpend ( companyId ) ,
2026-03-08 20:56:13 +05:30
// only fetch when the providers tab is active — these queries trigger outbound
// network calls to provider quota apis; no need to run them on the spend tab.
enabled : ! ! selectedCompanyId && mainTab === "providers" ,
2026-03-08 17:11:08 +05:30
refetchInterval : 30_000 ,
staleTime : 10_000 ,
2026-02-17 12:24:48 -06:00
} ) ;
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
2026-03-08 17:11:08 +05:30
const { data : quotaData } = useQuery ( {
2026-03-08 19:04:27 +05:30
queryKey : queryKeys.usageQuotaWindows ( companyId ) ,
queryFn : ( ) = > costsApi . quotaWindows ( companyId ) ,
2026-03-08 20:56:13 +05:30
enabled : ! ! selectedCompanyId && mainTab === "providers" ,
2026-03-08 17:11:08 +05:30
// quota windows come from external provider apis; refresh every 5 minutes
refetchInterval : 300_000 ,
staleTime : 60_000 ,
} ) ;
// ---------- providers tab derived maps ----------
const byProvider = useMemo ( ( ) = > {
const map = new Map < string , CostByProviderModel [ ] > ( ) ;
for ( const row of providerData ? ? [ ] ) {
const arr = map . get ( row . provider ) ? ? [ ] ;
arr . push ( row ) ;
map . set ( row . provider , arr ) ;
}
return map ;
} , [ providerData ] ) ;
const weekSpendByProvider = useMemo ( ( ) = > {
const map = new Map < string , number > ( ) ;
for ( const row of weekData ? ? [ ] ) {
map . set ( row . provider , ( map . get ( row . provider ) ? ? 0 ) + row . costCents ) ;
}
return map ;
} , [ weekData ] ) ;
const windowSpendByProvider = useMemo ( ( ) = > {
const map = new Map < string , CostWindowSpendRow [ ] > ( ) ;
for ( const row of windowData ? ? [ ] ) {
const arr = map . get ( row . provider ) ? ? [ ] ;
arr . push ( row ) ;
map . set ( row . provider , arr ) ;
}
return map ;
} , [ windowData ] ) ;
const quotaWindowsByProvider = useMemo ( ( ) = > {
const map = new Map < string , QuotaWindow [ ] > ( ) ;
for ( const result of quotaData ? ? [ ] ) {
if ( result . ok && result . windows . length > 0 ) {
map . set ( result . provider , result . windows ) ;
}
}
return map ;
} , [ quotaData ] ) ;
// deficit notch: projected month-end spend vs pro-rata budget share (mtd only)
// memoized to avoid stale closure reads when summary and byProvider arrive separately
const deficitNotchByProvider = useMemo ( ( ) = > {
const map = new Map < string , boolean > ( ) ;
if ( preset !== "mtd" ) return map ;
const budget = spendData ? . summary . budgetCents ? ? 0 ;
if ( budget <= 0 ) return map ;
const totalSpend = spendData ? . summary . spendCents ? ? 0 ;
const now = new Date ( ) ;
const daysElapsed = now . getDate ( ) ;
const daysInMonth = new Date ( now . getFullYear ( ) , now . getMonth ( ) + 1 , 0 ) . getDate ( ) ;
for ( const [ providerKey , rows ] of byProvider ) {
const providerCostCents = rows . reduce ( ( s , r ) = > s + r . costCents , 0 ) ;
const providerShare = totalSpend > 0 ? providerCostCents / totalSpend : 0 ;
const providerBudget = budget * providerShare ;
if ( providerBudget <= 0 ) { map . set ( providerKey , false ) ; continue ; }
const burnRate = providerCostCents / Math . max ( daysElapsed , 1 ) ;
map . set ( providerKey , providerCostCents + burnRate * ( daysInMonth - daysElapsed ) > providerBudget ) ;
}
return map ;
} , [ preset , spendData , byProvider ] ) ;
2026-03-08 19:04:27 +05:30
const providers = useMemo ( ( ) = > Array . from ( byProvider . keys ( ) ) , [ byProvider ] ) ;
// derive effective provider synchronously so the tab body never flashes blank.
// when activeProvider is no longer in the providers list, fall back to "all".
const effectiveProvider =
activeProvider === "all" || providers . includes ( activeProvider )
? activeProvider
: "all" ;
2026-03-08 17:11:08 +05:30
2026-03-08 19:04:27 +05:30
// write the fallback back into state so subsequent renders and user interactions
// start from a consistent baseline — without this, activeProvider stays stale and
// any future setActiveProvider call would re-derive from the wrong base value.
useEffect ( ( ) = > {
if ( effectiveProvider !== activeProvider ) setActiveProvider ( "all" ) ;
} , [ effectiveProvider , activeProvider ] ) ;
// ---------- provider tab items (memoized — contains jsx, recreating on every render
// forces PageTabBar to diff the full item tree on every 30s poll tick).
// totals are derived from byProvider (already memoized on providerData) so this memo
// only rebuilds when the underlying data actually changes, not on every query refetch. ----------
const providerTabItems = useMemo ( ( ) = > {
2026-03-08 21:00:46 +05:30
// derive provider keys inline so this memo only rebuilds when byProvider changes,
// not on the extra tick caused by the derived `providers` memo also changing.
const providerKeys = Array . from ( byProvider . keys ( ) ) ;
const allTokens = providerKeys . reduce (
2026-03-08 19:04:27 +05:30
( s , p ) = > s + ( byProvider . get ( p ) ? . reduce ( ( a , r ) = > a + r . inputTokens + r . outputTokens , 0 ) ? ? 0 ) ,
0 ,
) ;
2026-03-08 21:00:46 +05:30
const allCents = providerKeys . reduce (
2026-03-08 19:04:27 +05:30
( s , p ) = > s + ( byProvider . get ( p ) ? . reduce ( ( a , r ) = > a + r . costCents , 0 ) ? ? 0 ) ,
0 ,
) ;
return [
{
value : "all" ,
label : (
< span className = "flex items-center gap-1.5" >
< span > All providers < / span >
2026-03-08 21:00:46 +05:30
{ providerKeys . length > 0 && (
2026-03-08 19:04:27 +05:30
< >
< span className = "text-xs text-muted-foreground font-mono" >
{ formatTokens ( allTokens ) }
< / span >
< span className = "text-xs text-muted-foreground" >
{ formatCents ( allCents ) }
< / span >
< / >
) }
< / span >
) ,
} ,
2026-03-08 21:00:46 +05:30
. . . providerKeys . map ( ( p ) = > ( {
2026-03-08 19:04:27 +05:30
value : p ,
2026-03-08 19:18:04 +05:30
label : < ProviderTabLabel provider = { p } rows = { byProvider . get ( p ) ? ? [ ] } / > ,
2026-03-08 19:04:27 +05:30
} ) ) ,
] ;
2026-03-08 21:00:46 +05:30
} , [ byProvider ] ) ;
2026-03-08 19:04:27 +05:30
// ---------- guard ----------
2026-03-08 17:11:08 +05:30
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
if ( ! selectedCompanyId ) {
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
return < EmptyState icon = { DollarSign } message = "Select a company to view costs." / > ;
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
}
2026-03-08 17:11:08 +05:30
// ---------- render ----------
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
return (
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
< div className = "space-y-6" >
2026-03-08 17:11:08 +05:30
{ /* date range selector */ }
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
< div className = "flex flex-wrap items-center gap-2" >
2026-03-08 17:11:08 +05:30
{ PRESET_KEYS . map ( ( p ) = > (
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
< Button
key = { p }
variant = { preset === p ? "secondary" : "ghost" }
size = "sm"
onClick = { ( ) = > setPreset ( p ) }
>
{ PRESET_LABELS [ p ] }
< / Button >
) ) }
{ preset === "custom" && (
< div className = "flex items-center gap-2 ml-2" >
< input
type = "date"
value = { customFrom }
onChange = { ( e ) = > setCustomFrom ( e . target . value ) }
2026-03-08 19:04:27 +05:30
className = "h-8 border border-input bg-background px-2 text-sm text-foreground"
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
/ >
< span className = "text-sm text-muted-foreground" > to < / span >
< input
type = "date"
value = { customTo }
onChange = { ( e ) = > setCustomTo ( e . target . value ) }
2026-03-08 19:04:27 +05:30
className = "h-8 border border-input bg-background px-2 text-sm text-foreground"
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
/ >
< / div >
) }
< / div >
2026-03-08 17:11:08 +05:30
{ /* main spend / providers tab switcher */ }
< Tabs value = { mainTab } onValueChange = { ( v ) = > setMainTab ( v as "spend" | "providers" ) } >
< TabsList >
< TabsTrigger value = "spend" > Spend < / TabsTrigger >
< TabsTrigger value = "providers" > Providers < / TabsTrigger >
< / TabsList >
{ /* ── spend tab ─────────────────────────────────────────────── */ }
< TabsContent value = "spend" className = "mt-4 space-y-4" >
2026-03-08 19:04:27 +05:30
{ spendLoading ? (
< PageSkeleton variant = "costs" / >
) : preset === "custom" && ! customReady ? (
2026-03-08 17:11:08 +05:30
< p className = "text-sm text-muted-foreground" > Select a start and end date to load data . < / p >
2026-03-08 19:04:27 +05:30
) : spendError ? (
< p className = "text-sm text-destructive" > { ( spendError as Error ) . message } < / p >
2026-03-08 17:11:08 +05:30
) : spendData ? (
< >
{ /* summary card */ }
< Card >
< CardContent className = "p-4 space-y-3" >
< div className = "flex items-center justify-between" >
< p className = "text-sm text-muted-foreground" > { PRESET_LABELS [ preset ] } < / p >
{ spendData . summary . budgetCents > 0 && (
< p className = "text-sm text-muted-foreground" >
{ spendData . summary . utilizationPercent } % utilized
< / p >
) }
< / div >
< p className = "text-2xl font-bold" >
{ formatCents ( spendData . summary . spendCents ) } { " " }
< span className = "text-base font-normal text-muted-foreground" >
{ spendData . summary . budgetCents > 0
? ` / ${ formatCents ( spendData . summary . budgetCents ) } `
: "Unlimited budget" }
< / span >
2026-02-20 12:56:04 -06:00
< / p >
2026-03-08 17:11:08 +05:30
{ spendData . summary . budgetCents > 0 && (
< div className = "w-full h-2 border border-border overflow-hidden" >
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
< div
2026-03-08 17:11:08 +05:30
className = { ` h-full transition-[width,background-color] duration-150 ${
spendData . summary . utilizationPercent > 90
? "bg-red-400"
: spendData . summary . utilizationPercent > 70
? "bg-yellow-400"
: "bg-green-400"
} ` }
style = { { width : ` ${ Math . min ( 100 , spendData . summary . utilizationPercent ) } % ` } }
/ >
< / div >
) }
< / CardContent >
< / Card >
{ /* by agent / by project */ }
< div className = "grid md:grid-cols-2 gap-4" >
< Card >
< CardContent className = "p-4" >
< h3 className = "text-sm font-semibold mb-3" > By Agent < / h3 >
{ spendData . byAgent . length === 0 ? (
< p className = "text-sm text-muted-foreground" > No cost events yet . < / p >
) : (
< div className = "space-y-2" >
2026-03-08 20:56:13 +05:30
{ spendData . byAgent . map ( ( row ) = > {
const modelRows = agentModelRows . get ( row . agentId ) ? ? [ ] ;
const isExpanded = expandedAgents . has ( row . agentId ) ;
const hasBreakdown = modelRows . length > 0 ;
return (
< div key = { row . agentId } >
< div
className = { ` flex items-start justify-between text-sm ${ hasBreakdown ? "cursor-pointer select-none" : "" } ` }
onClick = { ( ) = > hasBreakdown && toggleAgent ( row . agentId ) }
>
< div className = "flex items-center gap-2 min-w-0" >
{ hasBreakdown ? (
isExpanded
? < ChevronDown className = "w-3 h-3 shrink-0 text-muted-foreground" / >
: < ChevronRight className = "w-3 h-3 shrink-0 text-muted-foreground" / >
) : (
< span className = "w-3 h-3 shrink-0" / >
) }
< Identity name = { row . agentName ? ? row . agentId } size = "sm" / >
{ row . agentStatus === "terminated" && (
< StatusBadge status = "terminated" / >
) }
< / div >
< div className = "text-right shrink-0 ml-2" >
< span className = "font-medium block" > { formatCents ( row . costCents ) } < / span >
< span className = "text-xs text-muted-foreground block" >
in { formatTokens ( row . inputTokens ) } / out { formatTokens ( row . outputTokens ) } tok
< / span >
{ ( row . apiRunCount > 0 || row . subscriptionRunCount > 0 ) && (
< span className = "text-xs text-muted-foreground block" >
{ row . apiRunCount > 0 ? ` api runs: ${ row . apiRunCount } ` : null }
{ row . apiRunCount > 0 && row . subscriptionRunCount > 0 ? " | " : null }
{ row . subscriptionRunCount > 0
? ` subscription runs: ${ row . subscriptionRunCount } ( ${ formatTokens ( row . subscriptionInputTokens ) } in / ${ formatTokens ( row . subscriptionOutputTokens ) } out tok) `
: null }
< / span >
) }
< / div >
< / div >
{ isExpanded && modelRows . length > 0 && (
< div className = "ml-5 mt-1 mb-1 border-l border-border pl-3 space-y-1" >
{ modelRows . map ( ( m ) = > {
const totalAgentCents = row . costCents ;
const sharePct = totalAgentCents > 0
? Math . round ( ( m . costCents / totalAgentCents ) * 100 )
: 0 ;
return (
< div
key = { ` ${ m . provider } / ${ m . model } ` }
className = "flex items-start justify-between text-xs text-muted-foreground"
>
< div className = "min-w-0 truncate" >
< span className = "font-medium text-foreground" > { providerDisplayName ( m . provider ) } < / span >
< span className = "mx-1 text-border" > / < / span >
< span className = "font-mono" > { m . model } < / span >
< / div >
< div className = "text-right shrink-0 ml-2" >
< span className = "font-medium text-foreground block" >
{ formatCents ( m . costCents ) }
< span className = "font-normal text-muted-foreground ml-1" > ( { sharePct } % ) < / span >
< / span >
< span className = "block" >
in { formatTokens ( m . inputTokens ) } / out { formatTokens ( m . outputTokens ) } tok
< / span >
< / div >
< / div >
) ;
} ) }
< / div >
2026-03-08 17:11:08 +05:30
) }
< / div >
2026-03-08 20:56:13 +05:30
) ;
} ) }
2026-03-08 17:11:08 +05:30
< / div >
) }
< / CardContent >
< / Card >
< Card >
< CardContent className = "p-4" >
< h3 className = "text-sm font-semibold mb-3" > By Project < / h3 >
{ spendData . byProject . length === 0 ? (
< p className = "text-sm text-muted-foreground" > No project - attributed run costs yet . < / p >
) : (
< div className = "space-y-2" >
2026-03-08 19:04:27 +05:30
{ spendData . byProject . map ( ( row , i ) = > (
2026-03-08 17:11:08 +05:30
< div
2026-03-08 21:26:06 +05:30
key = { row . projectId ? ? ` unattributed- ${ i } ` }
2026-03-08 17:11:08 +05:30
className = "flex items-center justify-between text-sm"
>
< span className = "truncate" >
{ row . projectName ? ? row . projectId ? ? "Unattributed" }
2026-02-25 21:36:06 -06:00
< / span >
2026-03-08 17:11:08 +05:30
< span className = "font-medium" > { formatCents ( row . costCents ) } < / span >
< / div >
) ) }
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
< / div >
2026-03-08 17:11:08 +05:30
) }
< / CardContent >
< / Card >
< / div >
< / >
) : null }
< / TabsContent >
{ /* ── providers tab ─────────────────────────────────────────── */ }
< TabsContent value = "providers" className = "mt-4" >
{ preset === "custom" && ! customReady ? (
< p className = "text-sm text-muted-foreground" > Select a start and end date to load data . < / p >
) : (
2026-03-08 19:04:27 +05:30
< Tabs value = { effectiveProvider } onValueChange = { setActiveProvider } >
2026-03-08 17:11:08 +05:30
< PageTabBar
items = { providerTabItems }
2026-03-08 19:04:27 +05:30
value = { effectiveProvider }
2026-03-08 17:11:08 +05:30
/ >
< TabsContent value = "all" className = "mt-4" >
{ providers . length === 0 ? (
< p className = "text-sm text-muted-foreground" > No cost events in this period . < / p >
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
) : (
2026-03-08 17:11:08 +05:30
< div className = "grid md:grid-cols-2 gap-4" >
{ providers . map ( ( p ) = > (
< ProviderQuotaCard
key = { p }
provider = { p }
2026-03-08 21:39:19 +05:30
rows = { byProvider . get ( p ) ? ? [ ] }
2026-03-08 17:11:08 +05:30
budgetMonthlyCents = { spendData ? . summary . budgetCents ? ? 0 }
totalCompanySpendCents = { spendData ? . summary . spendCents ? ? 0 }
weekSpendCents = { weekSpendByProvider . get ( p ) ? ? 0 }
windowRows = { windowSpendByProvider . get ( p ) ? ? [ ] }
showDeficitNotch = { deficitNotchByProvider . get ( p ) ? ? false }
quotaWindows = { quotaWindowsByProvider . get ( p ) ? ? [ ] }
/ >
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
) ) }
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
< / div >
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
) }
2026-03-08 17:11:08 +05:30
< / TabsContent >
{ providers . map ( ( p ) = > (
< TabsContent key = { p } value = { p } className = "mt-4" >
< ProviderQuotaCard
provider = { p }
2026-03-08 21:39:19 +05:30
rows = { byProvider . get ( p ) ? ? [ ] }
2026-03-08 17:11:08 +05:30
budgetMonthlyCents = { spendData ? . summary . budgetCents ? ? 0 }
totalCompanySpendCents = { spendData ? . summary . spendCents ? ? 0 }
weekSpendCents = { weekSpendByProvider . get ( p ) ? ? 0 }
windowRows = { windowSpendByProvider . get ( p ) ? ? [ ] }
showDeficitNotch = { deficitNotchByProvider . get ( p ) ? ? false }
quotaWindows = { quotaWindowsByProvider . get ( p ) ? ? [ ] }
/ >
< / TabsContent >
) ) }
< / Tabs >
) }
< / TabsContent >
< / Tabs >
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
< / div >
) ;
}