import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import adminService, { type QuotaScope } from '../api/services/adminService';
import { Button } from '../components/ui/button';
import { Input } from '../components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../components/ui/select';
import { selectToken } from '../preferences/preferenceSlice';
import { fmtNumber } from './AdminUI';
import {
fmtUsd,
formToPolicy,
isEmptyForm,
policyToForm,
usagePercent,
type Budget,
type BudgetMode,
type QuotaForm,
type QuotaPolicy,
} from './quotaUtils';
const MODES: { value: BudgetMode; label: string }[] = [
{ value: 'inherit', label: 'Not set here' },
{ value: 'limit', label: 'Limit' },
{ value: 'unlimited', label: 'Unlimited' },
];
export function UsageBar({
label,
budget,
kind,
caption,
}: {
label: string;
budget: Budget;
kind: 'tokens' | 'cost';
caption?: string;
}) {
const fmt = (n: number) => (kind === 'cost' ? fmtUsd(n) : fmtNumber(n));
const percent = usagePercent(budget.used, budget.limit);
const tone =
percent >= 100
? 'bg-red-500'
: percent >= 80
? 'bg-amber-500'
: 'bg-[#7D54D1]';
return (
{label}
{fmt(budget.used)}
{budget.limit === null ? ' ยท no limit' : ` of ${fmt(budget.limit)}`}
{budget.limit !== null ? (
) : null}
{caption ? (
{caption}
) : null}
);
}
function BudgetField({
label,
hint,
mode,
value,
step,
onMode,
onValue,
}: {
label: string;
hint: string;
mode: BudgetMode;
value: string;
step: string;
onMode: (mode: BudgetMode) => void;
onValue: (value: string) => void;
}) {
return (
);
}
/**
* Edits the ``all``-bucket policy of one subject. Saving a form with neither
* budget set removes the policy, since a policy without an opinion is not stored.
*/
export default function QuotaEditor({
scope,
subjectId,
policy,
inheritHint,
onSaved,
}: {
scope: QuotaScope;
subjectId: string | null;
policy: QuotaPolicy | null;
inheritHint: string;
onSaved: () => void;
}) {
const token = useSelector(selectToken);
const [form, setForm] = useState(() => policyToForm(policy));
const [busy, setBusy] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setForm(policyToForm(policy));
setError(null);
}, [policy, scope, subjectId]);
const patch = (fields: Partial) =>
setForm((prev) => ({ ...prev, ...fields }));
const submit = async (request: () => Promise) => {
setBusy(true);
setError(null);
try {
const res = await request();
const json = await res.json().catch(() => ({}));
if (res.ok && json.success !== false) onSaved();
else setError(json.message || 'Could not save the quota.');
} catch {
setError('Could not save the quota.');
} finally {
setBusy(false);
}
};
const remove = () =>
submit(() => adminService.deleteQuota(scope, subjectId, 'all', token));
const save = () => {
if (isEmptyForm(form)) {
if (policy) remove();
return;
}
const result = formToPolicy(form);
if (!result.ok) {
setError(result.error);
return;
}
submit(() => adminService.setQuota(scope, subjectId, result.policy, token));
};
return (
{inheritHint}
patch({ tokenMode })}
onValue={(tokenLimit) => patch({ tokenLimit })}
/>
patch({ costMode })}
onValue={(costLimit) => patch({ costLimit })}
/>
{error ? (
{error}
) : null}
{policy ? (
) : null}
);
}