thread-master/apps/web/src/components/usage/UsageMeterBars.tsx

87 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from "react";
import { useI18n } from "../../i18n/I18nContext";
import { METER_META, type UsageMeter } from "../../lib/usageMeter";
export type MeterBarRow = {
meter: UsageMeter;
/** 已用點數platform only */
credits: number;
/** 平台呼叫次數(不含 byok */
count: number;
/** 分項點數硬上限(與 PLANS.soft_caps 同步,單位=點) */
soft_cap: number;
};
type Props = {
rows: MeterBarRow[];
unlimited?: boolean;
};
/**
* 分項:數字 = 已用點 / 上限點(與方案 monthly_credits 同一單位)。
* 超過達上限標紅hover 顯示平台呼叫次數。
*/
export function UsageMeterBars({ rows, unlimited = false }: Props) {
const { t } = useI18n();
const [active, setActive] = useState<UsageMeter | null>(null);
const ordered = rows
.slice()
.sort((a, b) => METER_META[a.meter].order - METER_META[b.meter].order);
return (
<ul className="hb-usage-mbars" onMouseLeave={() => setActive(null)}>
{ordered.map((row) => {
const label = t(`usage.meter.${row.meter}`);
const over = !unlimited && row.soft_cap > 0 && row.credits > row.soft_cap;
const atCap = !unlimited && row.soft_cap > 0 && row.credits >= row.soft_cap;
const pct =
row.soft_cap > 0
? Math.min(100, Math.round((row.credits / row.soft_cap) * 100))
: 0;
const on = active === row.meter;
return (
<li key={row.meter}>
<button
type="button"
className={`hb-usage-mbar${on ? " is-on" : ""}${over || atCap ? " is-over" : ""}`}
onMouseEnter={() => setActive(row.meter)}
onFocus={() => setActive(row.meter)}
aria-label={t("usage.meter.barAria", {
label,
count: row.count,
credits: row.credits,
cap: row.soft_cap,
})}
>
<span className="hb-usage-mbar__label">{label}</span>
<span className="hb-usage-mbar__track" aria-hidden>
<span
className="hb-usage-mbar__fill"
style={{ width: `${Math.max(pct, row.credits > 0 ? 3 : 0)}%` }}
/>
</span>
<span className="hb-usage-mbar__val">
<strong>{row.credits}</strong>
<span className="text-muted">/{row.soft_cap}</span>
<span className="hb-usage-mbar__unit">{t("usage.meter.pt")}</span>
{on ? (
<span className="hb-usage-mbar__pts">
{t("usage.meter.timesShort", { n: row.count })}
</span>
) : null}
{unlimited ? (
<span className="hb-usage-mbar__inf"></span>
) : over ? (
<span className="hb-usage-mbar__over">{t("usage.meter.over")}</span>
) : atCap ? (
<span className="hb-usage-mbar__over">{t("usage.meter.locked")}</span>
) : null}
</span>
</button>
</li>
);
})}
</ul>
);
}