feat(admin, i18n): enhance reports, draws, config, and player workflows
This commit is contained in:
@@ -421,6 +421,9 @@ export function DashboardAgentRankingCard({
|
||||
const v = metricValue(row);
|
||||
const pct = (Math.abs(v) / maxAbs) * 100;
|
||||
const color = barColor(row);
|
||||
const agentName = row.agent_name?.trim() || "-";
|
||||
const agentCode = row.agent_code?.trim() || "";
|
||||
const showCode = agentCode !== "" && agentCode !== agentName;
|
||||
return (
|
||||
<div key={row.agent_node_id} className="rounded-lg bg-muted/20 px-2 py-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
@@ -429,8 +432,10 @@ export function DashboardAgentRankingCard({
|
||||
#{idx + 1}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-xs font-medium">{row.agent_name || "-"}</p>
|
||||
<p className="truncate text-[11px] text-muted-foreground">{row.agent_code || ""}</p>
|
||||
<p className="truncate text-xs font-medium">{agentName}</p>
|
||||
{showCode ? (
|
||||
<p className="truncate text-[11px] text-muted-foreground">{agentCode}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 text-right text-xs font-semibold tabular-nums">
|
||||
|
||||
@@ -13,6 +13,38 @@ import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { DrawCurrentSnapshot } from "@/types/api/public-draw";
|
||||
|
||||
function formatDashboardDrawStatus(
|
||||
status: string,
|
||||
t: (key: string, options?: Record<string, unknown>) => string,
|
||||
): string {
|
||||
const lower = status.trim().toLowerCase();
|
||||
|
||||
switch (lower) {
|
||||
case "pending":
|
||||
return t("statusOptions.pending", { ns: "draws" });
|
||||
case "open":
|
||||
return t("statusOptions.open", { ns: "draws" });
|
||||
case "closing":
|
||||
return t("statusOptions.closing", { ns: "draws" });
|
||||
case "closed":
|
||||
return t("statusOptions.closed", { ns: "draws" });
|
||||
case "drawing":
|
||||
return t("statusOptions.drawing", { ns: "draws" });
|
||||
case "review":
|
||||
return t("statusOptions.review", { ns: "draws" });
|
||||
case "cooldown":
|
||||
return t("statusOptions.cooldown", { ns: "draws" });
|
||||
case "settling":
|
||||
return t("statusOptions.settling", { ns: "draws" });
|
||||
case "settled":
|
||||
return t("statusOptions.settled", { ns: "draws" });
|
||||
case "cancelled":
|
||||
return t("statusOptions.cancelled", { ns: "draws" });
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
function isOpenLikeStatus(status: string): boolean {
|
||||
const lower = status.toLowerCase();
|
||||
return lower.includes("open") || lower.includes("sale");
|
||||
@@ -29,7 +61,7 @@ export function DashboardCurrentDrawCard({
|
||||
drawId,
|
||||
loading = false,
|
||||
}: DashboardCurrentDrawCardProps): ReactElement {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const { t } = useTranslation(["dashboard", "draws"]);
|
||||
const formatDt = useAdminDateTimeFormatter();
|
||||
|
||||
if (loading) {
|
||||
@@ -54,6 +86,7 @@ export function DashboardCurrentDrawCard({
|
||||
}
|
||||
|
||||
const openLike = isOpenLikeStatus(hall.status);
|
||||
const statusLabel = formatDashboardDrawStatus(hall.status, t);
|
||||
|
||||
return (
|
||||
<Card className="admin-list-card overflow-hidden border-primary/15 py-0 shadow-sm">
|
||||
@@ -95,7 +128,7 @@ export function DashboardCurrentDrawCard({
|
||||
openLike ? "bg-emerald-500" : "bg-muted-foreground/70",
|
||||
)}
|
||||
/>
|
||||
{hall.status}
|
||||
{statusLabel}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -817,6 +817,11 @@ export function HotUsageBars({
|
||||
}): ReactElement {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const chartConfig = useMemo(() => buildUsageBarConfig(t("riskCapUsage")), [t]);
|
||||
const shortenPoolLabel = (value: string): string => {
|
||||
const normalized = value.trim();
|
||||
const trimmedMeta = normalized.replace(/\s*\(.+\)$/, "");
|
||||
return trimmedMeta.length > 10 ? `${trimmedMeta.slice(0, 10)}…` : trimmedMeta;
|
||||
};
|
||||
|
||||
const chartData = useMemo(
|
||||
() =>
|
||||
@@ -824,6 +829,7 @@ export function HotUsageBars({
|
||||
const pct = Math.min(100, Math.max(0, (row.usage_ratio ?? 0) * 100));
|
||||
return {
|
||||
number: row.normalized_number.trim(),
|
||||
displayNumber: shortenPoolLabel(row.normalized_number),
|
||||
usage: pct,
|
||||
fill: usageBarFill(pct),
|
||||
};
|
||||
@@ -855,15 +861,23 @@ export function HotUsageBars({
|
||||
<YAxis
|
||||
type="category"
|
||||
dataKey="number"
|
||||
width={72}
|
||||
width={compact ? 92 : 112}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => {
|
||||
const row = chartData.find((item) => item.number === value);
|
||||
return row?.displayNumber ?? value;
|
||||
}}
|
||||
tick={{ fontSize: 11, fontFamily: "var(--font-mono)" }}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
formatter={(value) => `${Number(value).toFixed(1)}%`}
|
||||
formatter={(value, _name, item) => {
|
||||
const payload = item?.payload as { number?: string } | undefined;
|
||||
const number = payload?.number ? `${payload.number} · ` : "";
|
||||
return `${number}${Number(value).toFixed(1)}%`;
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user