2026-02-05 06:38:50 -05:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { formatResetTime, calculatePercentage } from "./utils";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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: "🟡"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 23:23:08 -05:00
|
|
|
// 0-29% including 0% (out of quota) - show red
|
2026-02-05 06:38:50 -05:00
|
|
|
return {
|
|
|
|
|
text: "text-red-600 dark:text-red-400",
|
|
|
|
|
bg: "bg-red-500",
|
|
|
|
|
bgLight: "bg-red-500/10",
|
|
|
|
|
emoji: "🔴"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Quota Table Component - Table-based display for quota data
|
|
|
|
|
*/
|
2026-04-04 00:25:58 -04:00
|
|
|
export default function QuotaTable({ quotas = [], compact = false }) {
|
2026-02-05 06:38:50 -05:00
|
|
|
if (!quotas || quotas.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 00:25:58 -04:00
|
|
|
const cellPad = compact ? "py-1.5 px-2" : "py-2 px-3";
|
|
|
|
|
const nameText = compact ? "text-xs" : "text-sm";
|
|
|
|
|
const resetPrimary = compact ? "text-xs" : "text-sm";
|
|
|
|
|
const resetSecondary = compact ? "text-[10px] leading-tight" : "text-xs";
|
|
|
|
|
|
2026-02-05 06:38:50 -05:00
|
|
|
return (
|
|
|
|
|
<div className="overflow-x-auto">
|
2026-04-04 00:25:58 -04:00
|
|
|
<table className="w-full table-fixed text-left">
|
2026-02-05 06:38:50 -05:00
|
|
|
<tbody>
|
|
|
|
|
{quotas.map((quota, index) => {
|
|
|
|
|
const remaining = quota.remainingPercentage !== undefined
|
|
|
|
|
? Math.round(quota.remainingPercentage)
|
|
|
|
|
: calculatePercentage(quota.used, quota.total);
|
|
|
|
|
|
|
|
|
|
const colors = getColorClasses(remaining);
|
|
|
|
|
const countdown = formatResetTime(quota.resetAt);
|
|
|
|
|
const resetDisplay = formatResetTimeDisplay(quota.resetAt);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<tr
|
|
|
|
|
key={index}
|
|
|
|
|
className="border-b border-black/5 dark:border-white/5 hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors"
|
|
|
|
|
>
|
|
|
|
|
{/* Model Name with Status Emoji */}
|
2026-04-15 00:44:46 -04:00
|
|
|
<td className={`${cellPad} w-[30%]`}>
|
2026-04-04 00:25:58 -04:00
|
|
|
<div className="flex items-center gap-1.5 min-w-0">
|
|
|
|
|
<span className="text-[10px] shrink-0">{colors.emoji}</span>
|
|
|
|
|
<span className={`${nameText} font-medium text-text-primary truncate`}>
|
|
|
|
|
{quota.name}
|
|
|
|
|
</span>
|
2026-02-05 06:38:50 -05:00
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
|
|
|
|
|
{/* Limit (Progress + Numbers) */}
|
2026-04-15 00:44:46 -04:00
|
|
|
<td className={`${cellPad} w-[45%]`}>
|
2026-04-04 00:25:58 -04:00
|
|
|
<div className={compact ? "space-y-1" : "space-y-1.5"}>
|
2026-02-05 06:38:50 -05:00
|
|
|
{/* Progress bar - always show with border for visibility */}
|
2026-04-04 00:25:58 -04:00
|
|
|
<div className={`${compact ? "h-1" : "h-1.5"} rounded-full overflow-hidden border ${colors.bgLight} ${
|
2026-02-05 06:38:50 -05:00
|
|
|
remaining === 0 ? 'border-black/10 dark:border-white/10' : 'border-transparent'
|
|
|
|
|
}`}>
|
|
|
|
|
<div
|
|
|
|
|
className={`h-full transition-all duration-300 ${colors.bg}`}
|
|
|
|
|
style={{ width: `${Math.min(remaining, 100)}%` }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Numbers */}
|
2026-04-04 00:25:58 -04:00
|
|
|
<div className={`flex items-center justify-between ${compact ? "text-[10px]" : "text-xs"}`}>
|
2026-02-05 06:38:50 -05:00
|
|
|
<span className="text-text-muted">
|
|
|
|
|
{quota.used.toLocaleString()} / {quota.total > 0 ? quota.total.toLocaleString() : "∞"}
|
|
|
|
|
</span>
|
|
|
|
|
<span className={`font-medium ${colors.text}`}>
|
|
|
|
|
{remaining}%
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
|
|
|
|
|
{/* Reset Time */}
|
2026-04-15 00:44:46 -04:00
|
|
|
<td className={`${cellPad} w-[25%]`}>
|
2026-02-05 23:23:08 -05:00
|
|
|
{countdown !== "-" || resetDisplay ? (
|
|
|
|
|
<div className="space-y-0.5">
|
|
|
|
|
{countdown !== "-" && (
|
2026-04-04 00:25:58 -04:00
|
|
|
<div className={`${resetPrimary} text-text-primary font-medium`}>
|
2026-02-05 23:23:08 -05:00
|
|
|
in {countdown}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{resetDisplay && (
|
2026-04-04 00:25:58 -04:00
|
|
|
<div className={`${resetSecondary} text-text-muted`}>
|
2026-02-05 23:23:08 -05:00
|
|
|
{resetDisplay}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2026-04-04 00:25:58 -04:00
|
|
|
<div className={`${resetPrimary} text-text-muted italic`}>N/A</div>
|
2026-02-05 23:23:08 -05:00
|
|
|
)}
|
2026-02-05 06:38:50 -05:00
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|