fix(admin): rupee formatting for avg revenue, per-tier storage bars
- Avg per Family now shows ₹ with en-IN locale instead of $ - Storage table: paid families show progress bar vs 25 GB limit (was "Unlimited") - Column header changed from "% of free (1 GiB)" to "% of limit" - Bar label shows "X% of 1 GiB" (free) or "X% of 25 GB" (paid) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4d29ef89a0
commit
bf82ef3364
3 changed files with 17 additions and 15 deletions
File diff suppressed because one or more lines are too long
|
|
@ -117,7 +117,7 @@ export default function AdminDashboard() {
|
||||||
<div className="text-gray-400 text-sm">Free Families</div>
|
<div className="text-gray-400 text-sm">Free Families</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-2xl font-bold text-amber-400">${overview.avgRevenuePerUser}</div>
|
<div className="text-2xl font-bold text-amber-400">₹{(overview.avgRevenuePerUser || 0).toLocaleString("en-IN")}</div>
|
||||||
<div className="text-gray-400 text-sm">Avg per Family</div>
|
<div className="text-gray-400 text-sm">Avg per Family</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -116,12 +116,16 @@ export default function AdminStorage() {
|
||||||
<th className="px-4 py-3 text-left text-sm font-medium">Tier</th>
|
<th className="px-4 py-3 text-left text-sm font-medium">Tier</th>
|
||||||
<th className="px-4 py-3 text-right text-sm font-medium">Objects</th>
|
<th className="px-4 py-3 text-right text-sm font-medium">Objects</th>
|
||||||
<th className="px-4 py-3 text-right text-sm font-medium">Storage</th>
|
<th className="px-4 py-3 text-right text-sm font-medium">Storage</th>
|
||||||
<th className="px-4 py-3 text-left text-sm font-medium w-48">% of free (1 GiB)</th>
|
<th className="px-4 py-3 text-left text-sm font-medium w-56">% of limit</th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody className="divide-y divide-gray-700">
|
<tbody className="divide-y divide-gray-700">
|
||||||
{families.map(f => {
|
{families.map(f => {
|
||||||
const pct = Math.min(Math.round(f.fraction * 100), 999);
|
const limit = f.isPaid ? 25 * 1024 * 1024 * 1024 : 1_073_741_824;
|
||||||
const barColor = f.overLimit ? "bg-rose-500" : f.fraction >= 0.8 ? "bg-amber-500" : "bg-emerald-500";
|
const fraction = f.bytes / limit;
|
||||||
|
const pct = Math.min(Math.round(fraction * 100), 999);
|
||||||
|
const isOver = fraction >= 1;
|
||||||
|
const barColor = isOver ? "bg-rose-500" : fraction >= 0.8 ? "bg-amber-500" : "bg-emerald-500";
|
||||||
|
const limitLabel = f.isPaid ? "25 GB" : "1 GiB";
|
||||||
return (
|
return (
|
||||||
<tr key={f.id} className="hover:bg-gray-750">
|
<tr key={f.id} className="hover:bg-gray-750">
|
||||||
<td className="px-4 py-3 font-medium">{f.name}</td>
|
<td className="px-4 py-3 font-medium">{f.name}</td>
|
||||||
|
|
@ -134,16 +138,14 @@ export default function AdminStorage() {
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right text-sm font-medium">{fmtBytes(f.bytes)}</td>
|
<td className="px-4 py-3 text-right text-sm font-medium">{fmtBytes(f.bytes)}</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
{f.isPaid ? (
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-rose-400">Unlimited (paid)</span>
|
<div className="flex-1 bg-gray-700 rounded-full h-2 overflow-hidden">
|
||||||
) : (
|
<div className={`h-full ${barColor}`} style={{ width: `${Math.min(pct, 100)}%` }} />
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex-1 bg-gray-700 rounded-full h-2 overflow-hidden">
|
|
||||||
<div className={`h-full ${barColor}`} style={{ width: `${Math.min(pct, 100)}%` }} />
|
|
||||||
</div>
|
|
||||||
<span className={`text-xs w-12 text-right ${f.overLimit ? "text-rose-400 font-bold" : "text-gray-400"}`}>{pct}%</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<span className={`text-xs w-24 text-right ${isOver ? "text-rose-400 font-bold" : "text-gray-400"}`}>
|
||||||
|
{pct}% of {limitLabel}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
@ -155,7 +157,7 @@ export default function AdminStorage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
Usage = SUM(size_bytes) over memories + attachments, matching the quota enforced in the app.
|
Usage = SUM(size_bytes) over memories + attachments. Limits: free = 1 GiB · paid = 25 GB.
|
||||||
Objects missing a recorded size aren't counted in bytes. R2 cost is storage only (egress is free on R2).
|
Objects missing a recorded size aren't counted in bytes. R2 cost is storage only (egress is free on R2).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue