- Add new "Quota Tracker" item to the sidebar navigation.
This commit is contained in:
parent
4e92a66379
commit
bfd9614fa2
7 changed files with 127 additions and 68 deletions
11
src/app/(dashboard)/dashboard/quota/page.js
Normal file
11
src/app/(dashboard)/dashboard/quota/page.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Suspense } from "react";
|
||||
import { CardSkeleton } from "@/shared/components/Loading";
|
||||
import ProviderLimits from "../usage/components/ProviderLimits";
|
||||
|
||||
export default function QuotaPage() {
|
||||
return (
|
||||
<Suspense fallback={<CardSkeleton />}>
|
||||
<ProviderLimits />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import QuotaTable from "./QuotaTable";
|
|||
import { parseQuotaData, calculatePercentage } from "./utils";
|
||||
import Card from "@/shared/components/Card";
|
||||
import Button from "@/shared/components/Button";
|
||||
import { CardSkeleton } from "@/shared/components/Loading";
|
||||
import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
|
||||
|
||||
const REFRESH_INTERVAL_MS = 60000; // 60 seconds
|
||||
|
|
@ -21,7 +20,7 @@ export default function ProviderLimits() {
|
|||
const [lastUpdated, setLastUpdated] = useState(null);
|
||||
const [refreshingAll, setRefreshingAll] = useState(false);
|
||||
const [countdown, setCountdown] = useState(60);
|
||||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
const [connectionsLoading, setConnectionsLoading] = useState(true);
|
||||
|
||||
const intervalRef = useRef(null);
|
||||
const countdownRef = useRef(null);
|
||||
|
|
@ -142,12 +141,26 @@ export default function ProviderLimits() {
|
|||
}
|
||||
}, [refreshingAll, fetchConnections, fetchQuota]);
|
||||
|
||||
// Initial load
|
||||
// Initial load: fetch connections first so cards render immediately, then fetch quotas
|
||||
useEffect(() => {
|
||||
const initializeData = async () => {
|
||||
setInitialLoading(true);
|
||||
await refreshAll();
|
||||
setInitialLoading(false);
|
||||
setConnectionsLoading(true);
|
||||
const conns = await fetchConnections();
|
||||
setConnectionsLoading(false);
|
||||
|
||||
const oauthConnections = conns.filter(
|
||||
(conn) => USAGE_SUPPORTED_PROVIDERS.includes(conn.provider) && conn.authType === "oauth"
|
||||
);
|
||||
|
||||
// Mark all as loading before fetching
|
||||
const loadingState = {};
|
||||
oauthConnections.forEach((conn) => { loadingState[conn.id] = true; });
|
||||
setLoading(loadingState);
|
||||
|
||||
await Promise.all(
|
||||
oauthConnections.map((conn) => fetchQuota(conn.id, conn.provider))
|
||||
);
|
||||
setLastUpdated(new Date());
|
||||
};
|
||||
|
||||
initializeData();
|
||||
|
|
@ -271,22 +284,8 @@ export default function ProviderLimits() {
|
|||
return count + (hasLowQuota ? 1 : 0);
|
||||
}, 0);
|
||||
|
||||
// Initial loading state
|
||||
if (initialLoading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<CardSkeleton />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Empty state
|
||||
if (sortedConnections.length === 0) {
|
||||
if (!connectionsLoading && sortedConnections.length === 0) {
|
||||
return (
|
||||
<Card padding="lg">
|
||||
<div className="text-center py-12">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
import { Suspense, useState } from "react";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import { UsageStats, RequestLogger, CardSkeleton, SegmentedControl } from "@/shared/components";
|
||||
import ProviderLimits from "./components/ProviderLimits";
|
||||
import RequestDetailsTab from "./components/RequestDetailsTab";
|
||||
|
||||
export default function UsagePage() {
|
||||
|
|
@ -21,7 +20,7 @@ function UsageContent() {
|
|||
const [tabLoading, setTabLoading] = useState(false);
|
||||
|
||||
const tabFromUrl = searchParams.get("tab");
|
||||
const activeTab = tabFromUrl && ["overview", "logs", "limits", "details"].includes(tabFromUrl)
|
||||
const activeTab = tabFromUrl && ["overview", "logs", "details"].includes(tabFromUrl)
|
||||
? tabFromUrl
|
||||
: "overview";
|
||||
|
||||
|
|
@ -40,7 +39,6 @@ function UsageContent() {
|
|||
<SegmentedControl
|
||||
options={[
|
||||
{ value: "overview", label: "Overview" },
|
||||
{ value: "limits", label: "Limits" },
|
||||
{ value: "details", label: "Details" },
|
||||
]}
|
||||
value={activeTab}
|
||||
|
|
@ -57,11 +55,6 @@ function UsageContent() {
|
|||
</Suspense>
|
||||
)}
|
||||
{activeTab === "logs" && <RequestLogger />}
|
||||
{activeTab === "limits" && (
|
||||
<Suspense fallback={<CardSkeleton />}>
|
||||
<ProviderLimits />
|
||||
</Suspense>
|
||||
)}
|
||||
{activeTab === "details" && <RequestDetailsTab />}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue