diff --git a/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx b/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx index 97e2e3ba63..42f78f897a 100644 --- a/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx +++ b/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx @@ -6,16 +6,16 @@ import { Button } from "@/shared/components"; import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle"; import useEmailPrivacyStore from "@/store/emailPrivacyStore"; import { maskEmailLikeValue } from "@/shared/utils/maskEmail"; -import type { QuotaPool, PoolAllocation } from "@/lib/quota/dimensions"; +import type { QuotaPool } from "@/lib/quota/dimensions"; import { usePools } from "./hooks/usePools"; import { usePoolUsage } from "./hooks/usePoolUsage"; import { useLocalStoragePoolMigration } from "./hooks/useLocalStoragePoolMigration"; import { usePoolsUsageAggregate } from "./hooks/usePoolsUsageAggregate"; import QuotaConceptCard from "./components/QuotaConceptCard"; +import QuotaEndpointsCard from "./components/QuotaEndpointsCard"; import PoolCard from "./components/PoolCard"; import PoolWizard from "./components/PoolWizard"; -import EditAllocationsModal from "./components/EditAllocationsModal"; // ──────────────────────────────────────────────────────────────────────────── // Local types (display layer only) @@ -142,7 +142,7 @@ export default function QuotaSharePageClient() { // ── Group state ─────────────────────────────────────────────────────────── const [groups, setGroups] = useState([]); - const [selectedGroupId, setSelectedGroupId] = useState("group-demo"); + const [selectedGroupId, setSelectedGroupId] = useState("all"); const [newGroupInput, setNewGroupInput] = useState(""); const [showNewGroupInput, setShowNewGroupInput] = useState(false); const [renaming, setRenaming] = useState(false); @@ -286,26 +286,43 @@ export default function QuotaSharePageClient() { [pools, aggregate] ); - // Pools filtered by selected group + // Pools filtered by selected group (kept for stats/empty-state checks) const filteredPools = useMemo( - () => pools.filter((p) => (p as unknown as { groupId?: string }).groupId === selectedGroupId || (!( p as unknown as { groupId?: string }).groupId && selectedGroupId === "group-demo")), + () => + selectedGroupId === "all" + ? pools + : pools.filter( + (p) => + ((p as unknown as { groupId?: string }).groupId ?? "group-demo") === selectedGroupId + ), [pools, selectedGroupId] ); - // ── Mutations ───────────────────────────────────────────────────────────── - - const handleSaveAllocations = useCallback( - async (pool: QuotaPool, allocations: PoolAllocation[]) => { - await fetch(`/api/quota/pools/${pool.id}`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ allocations }), - }); - await mutate(); - }, - [mutate] + // Groups to render as stacked sections + const groupsToRender = useMemo( + () => (selectedGroupId === "all" ? groups : groups.filter((g) => g.id === selectedGroupId)), + [groups, selectedGroupId] ); + // ── Computed exclusivity for the pool being edited ─────────────────────── + // + // A pool is exclusive when it has ≥1 allocation AND every allocated key + // currently has the pool id in its allowedQuotas array. + + const editingExclusive = useMemo( + () => + !!editing && + editing.allocations.length > 0 && + editing.allocations.every((a) => { + const k = apiKeys.find((kk) => kk.id === a.apiKeyId); + const aq = (k as { allowedQuotas?: string[] } | undefined)?.allowedQuotas; + return Array.isArray(aq) && aq.includes(editing.id); + }), + [editing, apiKeys] + ); + + // ── Mutations ───────────────────────────────────────────────────────────── + const handleRemovePool = useCallback( async (id: string) => { if (!confirm(t("removeConfirm"))) return; @@ -348,6 +365,7 @@ export default function QuotaSharePageClient() { title={t("groupSelectHint")} className="px-2 py-1 rounded border border-border bg-bg-base text-sm text-text-main min-w-[120px]" > + {groups.map((g) => (