diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js index 4bc47b044..e1065065c 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js @@ -585,21 +585,28 @@ export function parseQuotaData(provider, data) { case "qoder": // Qoder ships a `user` quota and (optionally) an `organization` // quota, both with same shape: {total, used, remaining, unit, resetAt}. - // Skip an organization bucket when its total is 0 — most personal - // Qoder accounts won't have one and rendering "0/0" is misleading. + /** + * Hide only all-zero organization placeholders; a zero reported total + * can still carry meaningful usage and is inferred from used + remaining. + */ // Don't forward Qoder's `remaining` field: it's an absolute credit // count, but getRemainingPercentage / QuotaTable interpret // `remaining` as a 0-100 percentage and would render 348 credits // as "348%". The percentage is computed from used/total instead. if (data.quotas) { Object.entries(data.quotas).forEach(([quotaType, quota]) => { - if (quotaType === "organization" && (!quota || (Number(quota.total) || 0) === 0)) { + if ( + quotaType === "organization" + && (!quota || [quota.total, quota.used, quota.remaining] + .every((value) => (Number(value) || 0) === 0)) + ) { return; } normalizedQuotas.push({ name: quotaType === "user" ? "Personal" : quotaType === "organization" ? "Organization" : quotaType, used: quota.used || 0, - total: quota.total || 0, + total: Math.max(0, Number(quota.total) || 0) + || ((Number(quota.used) || 0) + (Number(quota.remaining) || 0)), unit: quota.unit, resetAt: quota.resetAt || null, }); diff --git a/tests/unit/qoder-quota.test.js b/tests/unit/qoder-quota.test.js new file mode 100644 index 000000000..fefe59d30 --- /dev/null +++ b/tests/unit/qoder-quota.test.js @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; + +import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js"; + +describe("Qoder quota normalization", () => { + it("shows zero-total organization usage with an inferred total", () => { + expect(parseQuotaData("qoder", { + quotas: { + organization: { + total: 0, + used: 3804, + remaining: 6196, + unit: "credits", + resetAt: "2026-07-31T16:00:00.000Z", + }, + }, + })).toEqual([{ + name: "Organization", + used: 3804, + total: 10000, + unit: "credits", + resetAt: "2026-07-31T16:00:00.000Z", + }]); + }); + + it("keeps an all-zero personal quota", () => { + expect(parseQuotaData("qoder", { + quotas: { + user: { + total: 0, + used: 0, + remaining: 0, + unit: "credits", + }, + }, + })).toEqual([{ + name: "Personal", + used: 0, + total: 0, + unit: "credits", + resetAt: null, + }]); + }); + + it("skips an all-zero organization placeholder", () => { + expect(parseQuotaData("qoder", { + quotas: { + organization: { + total: 0, + used: 0, + remaining: 0, + unit: "credits", + }, + }, + })).toEqual([]); + }); +});