import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alertDialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { resetDurationLabels } from "@/lib/constants/governance"; import { getErrorMessage, useDeleteTeamMutation } from "@/lib/store"; import { Customer, Team, VirtualKey } from "@/lib/types/governance"; import { cn } from "@/lib/utils"; import { formatCurrency } from "@/lib/utils/governance"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { Input } from "@/components/ui/input"; import { ChevronLeft, ChevronRight, Edit, Plus, Search, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import TeamDialog from "./teamDialog"; import { TeamsEmptyState } from "./teamsEmptyState"; // Helper to format reset duration for display const formatResetDuration = (duration: string) => { return resetDurationLabels[duration] || duration; }; interface TeamsTableProps { teams: Team[]; totalCount: number; customers: Customer[]; virtualKeys: VirtualKey[]; search: string; debouncedSearch: string; onSearchChange: (value: string) => void; offset: number; limit: number; onOffsetChange: (offset: number) => void; } export default function TeamsTable({ teams, totalCount, customers, virtualKeys, search, debouncedSearch, onSearchChange, offset, limit, onOffsetChange, }: TeamsTableProps) { const [showTeamDialog, setShowTeamDialog] = useState(false); const [editingTeam, setEditingTeam] = useState(null); const hasCreateAccess = useRbac(RbacResource.Teams, RbacOperation.Create); const hasUpdateAccess = useRbac(RbacResource.Teams, RbacOperation.Update); const hasDeleteAccess = useRbac(RbacResource.Teams, RbacOperation.Delete); const [deleteTeam, { isLoading: isDeleting }] = useDeleteTeamMutation(); const handleDelete = async (teamId: string) => { try { await deleteTeam(teamId).unwrap(); toast.success("Team deleted successfully"); } catch (error) { toast.error(getErrorMessage(error)); } }; const handleAddTeam = () => { setEditingTeam(null); setShowTeamDialog(true); }; const handleEditTeam = (team: Team) => { setEditingTeam(team); setShowTeamDialog(true); }; const handleTeamSaved = () => { setShowTeamDialog(false); setEditingTeam(null); }; const getVirtualKeysForTeam = (teamId: string) => { return virtualKeys.filter((vk) => vk.team_id === teamId); }; const getCustomerName = (customerId?: string) => { if (!customerId) return "-"; const customer = customers.find((c) => c.id === customerId); return customer ? customer.name : "Unknown Customer"; }; const hasActiveFilters = debouncedSearch; // True empty state: no teams at all (not just filtered to zero) if (totalCount === 0 && !hasActiveFilters) { return ( <> {showTeamDialog && ( setShowTeamDialog(false)} /> )} ); } return ( <> {showTeamDialog && ( setShowTeamDialog(false)} /> )}

Teams

Organize users into teams with shared budgets and access controls.

onSearchChange(e.target.value)} className="pl-9" data-testid="teams-search-input" />
Name Customer Budget Rate Limit Virtual Keys {teams.length === 0 ? ( No matching teams found. ) : ( teams.map((team) => { const vks = getVirtualKeysForTeam(team.id); const customerName = getCustomerName(team.customer_id); // Budget calculations — any of the team's budgets exhausted const teamBudgets = team.budgets ?? []; const isBudgetExhausted = teamBudgets.some( (b) => b.max_limit > 0 && b.current_usage >= b.max_limit, ); // Rate limit calculations const isTokenLimitExhausted = team.rate_limit?.token_max_limit && team.rate_limit.token_max_limit > 0 && team.rate_limit.token_current_usage >= team.rate_limit.token_max_limit; const isRequestLimitExhausted = team.rate_limit?.request_max_limit && team.rate_limit.request_max_limit > 0 && team.rate_limit.request_current_usage >= team.rate_limit.request_max_limit; const isRateLimitExhausted = isTokenLimitExhausted || isRequestLimitExhausted; const tokenPercentage = team.rate_limit?.token_max_limit && team.rate_limit.token_max_limit > 0 ? Math.min((team.rate_limit.token_current_usage / team.rate_limit.token_max_limit) * 100, 100) : 0; const requestPercentage = team.rate_limit?.request_max_limit && team.rate_limit.request_max_limit > 0 ? Math.min((team.rate_limit.request_current_usage / team.rate_limit.request_max_limit) * 100, 100) : 0; const isExhausted = isBudgetExhausted || isRateLimitExhausted; return (
{team.name} {isExhausted && ( Limit Reached )}
{customerName}
{teamBudgets.length > 0 ? (
{teamBudgets.map((b) => { const budgetPercentage = b.max_limit > 0 ? Math.min((b.current_usage / b.max_limit) * 100, 100) : 0; const isExhausted = b.max_limit > 0 && b.current_usage >= b.max_limit; return (
{formatCurrency(b.max_limit)} {formatResetDuration(b.reset_duration)}
div]:bg-red-500/70" : budgetPercentage > 80 ? "[&>div]:bg-amber-500/70" : "[&>div]:bg-emerald-500/70", )} />

{formatCurrency(b.current_usage)} / {formatCurrency(b.max_limit)}

Resets {formatResetDuration(b.reset_duration)}

); })}
) : ( - )}
{team.rate_limit ? (
{team.rate_limit.token_max_limit && (
{team.rate_limit.token_max_limit.toLocaleString()} tokens {formatResetDuration(team.rate_limit.token_reset_duration || "1h")}
div]:bg-red-500/70" : tokenPercentage > 80 ? "[&>div]:bg-amber-500/70" : "[&>div]:bg-emerald-500/70", )} />

{team.rate_limit.token_current_usage.toLocaleString()} /{" "} {team.rate_limit.token_max_limit.toLocaleString()} tokens

Resets {formatResetDuration(team.rate_limit.token_reset_duration || "1h")}

)} {team.rate_limit.request_max_limit && (
{team.rate_limit.request_max_limit.toLocaleString()} req {formatResetDuration(team.rate_limit.request_reset_duration || "1h")}
div]:bg-red-500/70" : requestPercentage > 80 ? "[&>div]:bg-amber-500/70" : "[&>div]:bg-emerald-500/70", )} />

{team.rate_limit.request_current_usage.toLocaleString()} /{" "} {team.rate_limit.request_max_limit.toLocaleString()} requests

Resets {formatResetDuration(team.rate_limit.request_reset_duration || "1h")}

)}
) : ( - )}
{vks.length > 0 ? (
{vks.length} {vks.length === 1 ? "key" : "keys"} {vks.map((vk) => vk.name).join(", ")}
) : ( - )}
Delete Team Are you sure you want to delete "{team.name}"? This will also unassign any virtual keys from this team. This action cannot be undone. Cancel handleDelete(team.id)} disabled={isDeleting} className="bg-red-600 hover:bg-red-700" > {isDeleting ? "Deleting..." : "Delete"}
); }) )}
{/* Pagination */} {totalCount > 0 && (

Showing {offset + 1}-{Math.min(offset + limit, totalCount)} of {totalCount}

)}
); }