import { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import adminService from '../api/services/adminService'; import teamsService from '../api/services/teamsService'; import { Modal } from '../components/ui/modal'; import { selectToken } from '../preferences/preferenceSlice'; import { LoadError, Loading, fmtDate } from './AdminUI'; import QuotaEditor, { UsageBar } from './QuotaEditor'; import { sourceLabel, type BucketStatus, type Budget, type QuotaPolicy, } from './quotaUtils'; /** A user's effective limits and usage, with the editor for their override. */ export default function UserQuotaModal({ userId, onClose, }: { userId: string | null; onClose: () => void; }) { const token = useSelector(selectToken); const [data, setData] = useState(null); const [teamNames, setTeamNames] = useState>({}); const load = useCallback(async () => { if (!userId) return; setData(null); try { const [res, teamsJson] = await Promise.all([ adminService.getUserQuota(userId, token), teamsService.listAll(token).catch(() => ({})), ]); setData(await res.json().catch(() => ({ success: false }))); setTeamNames( Object.fromEntries( (teamsJson?.teams ?? []).map((team: any) => [ String(team.id), team.name, ]), ), ); } catch { setData({ success: false }); } }, [userId, token]); useEffect(() => { load(); }, [load]); const overall: BucketStatus | undefined = (data?.effective ?? []).find( (status: BucketStatus) => status.bucket === 'all', ); const override: QuotaPolicy | null = (data?.policies ?? []).find((p: QuotaPolicy) => p.bucket === 'all') ?? null; const caption = (budget: Budget) => sourceLabel( budget, budget.source_id ? teamNames[budget.source_id] : undefined, ); return ( { if (!open) onClose(); }} title={userId ? `Quota · ${userId}` : 'Quota'} > {data === null ? ( ) : !data.success ? ( ) : (
{overall ? (

Resets {fmtDate(overall.resets_at)}

) : null}

User override

)}
); }