diff --git a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx index bd131e0841..657d505294 100644 --- a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx +++ b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx @@ -17,8 +17,10 @@ export interface PoolCardProps { keyLabels: Record; /** Connection display label */ connectionLabel: string; - /** Provider identifier */ + /** Primary provider identifier */ provider: string; + /** Optional list of all provider identifiers when pool has multiple connections */ + providers?: string[]; onEdit: () => void; onRemove: () => void; } @@ -47,6 +49,7 @@ export default function PoolCard({ keyLabels, connectionLabel, provider, + providers, onEdit, onRemove, }: PoolCardProps) { @@ -62,9 +65,28 @@ export default function PoolCard({ {/* Header */}
-
- -
+ {/* Provider icon(s): show one per provider when multi-connection, else single icon */} + {providers && providers.length > 1 ? ( +
+ {providers.slice(0, 3).map((p) => ( +
+ +
+ ))} + {providers.length > 3 && ( + + +{providers.length - 3} + + )} +
+ ) : ( +
+ +
+ )}
diff --git a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx index 55ff9440e4..cb4fb48912 100644 --- a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx +++ b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx @@ -165,7 +165,8 @@ export default function PoolWizard({ const [step, setStep] = useState<1 | 2 | 3>(1); // ── Step 1 state ────────────────────────────────────────────────────────── - const [connectionId, setConnectionId] = useState(""); + // Multi-select: ordered array of selected connection IDs. First element is primary. + const [connectionIds, setConnectionIds] = useState([]); const [poolName, setPoolName] = useState(""); const [defaultPolicy, setDefaultPolicy] = useState("hard"); @@ -181,14 +182,18 @@ export default function PoolWizard({ const [saving, setSaving] = useState(false); const [error, setError] = useState(null); + // ── Derived state ───────────────────────────────────────────────────────── + // The first selected connection is the "primary" — used for plan fetch/PUT. + const primaryConnectionId = connectionIds[0] ?? ""; + // ── Helpers ─────────────────────────────────────────────────────────────── const connLabel = (c: Connection) => `${c.provider} / ${c.name || c.email || c.displayName || c.id.slice(0, 12)}`; const selectedConn = useMemo( - () => connections.find((c) => c.id === connectionId), - [connections, connectionId] + () => connections.find((c) => c.id === primaryConnectionId), + [connections, primaryConnectionId] ); const availableConnections = useMemo( @@ -196,15 +201,15 @@ export default function PoolWizard({ [connections, existingPoolConnectionIds] ); - // ── Load dimensions when connection changes ─────────────────────────────── + // ── Load dimensions when primary connection changes ─────────────────────── useEffect(() => { - if (!connectionId) { + if (!primaryConnectionId) { setEditDimensions([]); setDimensionsEdited(false); return; } - const existingPlan = plans[connectionId]; + const existingPlan = plans[primaryConnectionId]; if (existingPlan && existingPlan.dimensions.length > 0) { setEditDimensions([...existingPlan.dimensions]); } else { @@ -213,14 +218,14 @@ export default function PoolWizard({ } setDimensionsEdited(false); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [connectionId]); + }, [primaryConnectionId]); // ── Reset wizard on open/close ──────────────────────────────────────────── useEffect(() => { if (!open) { setStep(1); - setConnectionId(""); + setConnectionIds([]); setPoolName(""); setDefaultPolicy("hard"); setEditDimensions([]); @@ -296,21 +301,35 @@ export default function PoolWizard({ // ── Preview model names ─────────────────────────────────────────────────── - const previewNames = useMemo(() => { - if (!selectedConn || !poolName.trim()) return []; - const models = getPreviewModels(selectedConn.provider); - const MAX = 6; - return models.slice(0, MAX).map((m) => - quotaModelName(poolName.trim(), selectedConn.provider, m) - ); - }, [selectedConn, poolName]); + // Per-provider preview: { provider, names[], totalModels } + const previewByProvider = useMemo(() => { + const name = poolName.trim(); + if (connectionIds.length === 0 || !name) return []; + + const MAX_PER_PROVIDER = 3; + return connectionIds.map((cid) => { + const conn = connections.find((c) => c.id === cid); + if (!conn) return null; + const allModels = getPreviewModels(conn.provider); + const names = allModels.slice(0, MAX_PER_PROVIDER).map((m) => + quotaModelName(name, conn.provider, m) + ); + return { provider: conn.provider, names, totalModels: allModels.length }; + }).filter(Boolean) as Array<{ provider: string; names: string[]; totalModels: number }>; + }, [connectionIds, connections, poolName]); + + // Flat list (for legacy single-provider path, kept for step-3 rendering simplicity) + const previewNames = useMemo( + () => previewByProvider.flatMap((p) => p.names), + [previewByProvider] + ); const effectivePoolName = poolName.trim() || (selectedConn ? connLabel(selectedConn) : ""); // ── Save sequence ───────────────────────────────────────────────────────── const handleFinish = async () => { - if (!selectedConn) return; + if (!selectedConn || connectionIds.length === 0) return; setSaving(true); setError(null); @@ -320,7 +339,8 @@ export default function PoolWizard({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - connectionId, + connectionId: primaryConnectionId, + connectionIds, name: effectivePoolName, allocations: [], }), @@ -334,9 +354,9 @@ export default function PoolWizard({ const createData = (await createRes.json()) as { pool: { id: string } }; const newPoolId = createData.pool.id; - // 2. PUT /api/quota/plans/[connectionId] — only when user edited dimensions + // 2. PUT /api/quota/plans/[primaryConnectionId] — only when user edited dimensions if (dimensionsEdited && editDimensions.length > 0) { - const planRes = await fetch(`/api/quota/plans/${connectionId}`, { + const planRes = await fetch(`/api/quota/plans/${primaryConnectionId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ dimensions: editDimensions }), @@ -390,40 +410,72 @@ export default function PoolWizard({

{t("wizardStep1Subtitle")}

- {/* Connection selector */} + {/* Connection multi-select (checkboxes) */}
- { + setConnectionIds((prev) => { + if (prev.includes(c.id)) { + const next = prev.filter((id) => id !== c.id); + if (next.length === 0) setPoolName(""); + return next; + } else { + return [...prev, c.id]; + } + }); + }} + className="accent-primary w-3.5 h-3.5 shrink-0" + /> + {connLabel(c)} + {isPrimary && ( + + {t("wizardPrimaryBadge")} + + )} + + ); + })} {connections .filter((c) => existingPoolConnectionIds.has(c.id)) .map((c) => ( - + ))} - +
{connections.length === 0 && (

{t("noEligibleConnections")}

)}
{/* Pool name */} - {connectionId && ( + {connectionIds.length > 0 && (