"use client"; import { useEffect, useMemo, useState } from "react"; import { formatResetTime, getRemainingPercentage } from "./utils"; const PAGE_SIZE = 10; /** * Format reset time display (Today, 12:00 PM) */ function formatResetTimeDisplay(resetTime) { if (!resetTime) return null; try { const date = new Date(resetTime); const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); let dayStr = ""; if (date >= today && date < tomorrow) { dayStr = "Today"; } else if (date >= tomorrow && date < new Date(tomorrow.getTime() + 24 * 60 * 60 * 1000)) { dayStr = "Tomorrow"; } else { dayStr = date.toLocaleDateString("en-US", { month: "short", day: "numeric" }); } const timeStr = date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true, }); return `${dayStr}, ${timeStr}`; } catch { return null; } } /** * Get color classes based on remaining percentage */ function getColorClasses(remainingPercentage) { if (remainingPercentage > 70) { return { text: "text-green-600 dark:text-green-400", bg: "bg-green-500", bgLight: "bg-green-500/10", emoji: "🟢", }; } if (remainingPercentage >= 30) { return { text: "text-yellow-600 dark:text-yellow-400", bg: "bg-yellow-500", bgLight: "bg-yellow-500/10", emoji: "🟡", }; } return { text: "text-red-600 dark:text-red-400", bg: "bg-red-500", bgLight: "bg-red-500/10", emoji: "🔴", }; } function sortQuotas(quotas, sortMode) { if (sortMode === "remaining-asc") { return [...quotas].sort((a, b) => a.remaining - b.remaining || a.name.localeCompare(b.name)); } if (sortMode === "remaining-desc") { return [...quotas].sort((a, b) => b.remaining - a.remaining || a.name.localeCompare(b.name)); } return quotas; } /** * Quota Table Component - Table-based display for quota data */ export default function QuotaTable({ quotas = [], compact = false, sortMode = "default", showSortLabel = false, }) { const [page, setPage] = useState(1); const normalizedQuotas = useMemo( () => quotas.map((quota, index) => ({ ...quota, index, remaining: getRemainingPercentage(quota), })), [quotas], ); const sortedQuotas = useMemo( () => sortQuotas(normalizedQuotas, sortMode), [normalizedQuotas, sortMode], ); const totalPages = Math.max(1, Math.ceil(sortedQuotas.length / PAGE_SIZE)); useEffect(() => { setPage(1); }, [sortMode, quotas]); useEffect(() => { setPage((currentPage) => Math.min(currentPage, totalPages)); }, [totalPages]); if (!quotas || quotas.length === 0) { return null; } const currentPageRows = sortedQuotas.slice( (page - 1) * PAGE_SIZE, page * PAGE_SIZE, ); const pageStart = sortedQuotas.length === 0 ? 0 : (page - 1) * PAGE_SIZE + 1; const pageEnd = Math.min(page * PAGE_SIZE, sortedQuotas.length); const cellPad = compact ? "py-1 px-1.5" : "py-2 px-3"; const nameText = compact ? "text-[11px]" : "text-sm"; const resetPrimary = compact ? "text-[11px]" : "text-sm"; const resetSecondary = compact ? "text-[10px] leading-tight" : "text-xs"; const sortLabel = "Sorted by account remaining"; return (
{sortedQuotas.length} quota{sortedQuotas.length > 1 ? "s" : ""}
{showSortLabel && (
{sortLabel}
)}
{currentPageRows.map((quota) => { const colors = getColorClasses(quota.remaining); const countdown = formatResetTime(quota.resetAt); const resetDisplay = formatResetTimeDisplay(quota.resetAt); return ( ); })}
{colors.emoji} {quota.name}
{quota.used.toLocaleString()} / {quota.total > 0 ? quota.total.toLocaleString() : "∞"} {quota.remaining}%
{countdown !== "-" || resetDisplay ? ( compact ? (
{countdown !== "-" ? `in ${countdown}` : resetDisplay}
) : (
{countdown !== "-" && (
in {countdown}
)} {resetDisplay && (
{resetDisplay}
)}
) ) : (
N/A
)}
{totalPages > 1 && (
Showing {pageStart}-{pageEnd} of {sortedQuotas.length} Page {page} / {totalPages}
)}
); }