feat(ui): enhance table and admin components with improved layout and status display

- Updated global CSS to center-align table headers and cells, ensuring a consistent layout.
- Modified admin table components to replace switches with status badges for better clarity.
- Enhanced internationalization support by adding new strings for version actions and validation messages in multiple locales.
- Refactored configuration document screens to include version selection and improved user feedback on status changes.
This commit is contained in:
2026-05-26 11:13:16 +08:00
parent 05fa0cbeec
commit 4080f0b601
38 changed files with 788 additions and 608 deletions

View File

@@ -312,20 +312,25 @@ function parsePositiveInteger(value: string): number | null {
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
}
async function resolveDraw(filters: ReportFilters): Promise<{ id: number; draw_no: string }> {
async function resolveDraw(
filters: ReportFilters,
t: (key: string, options?: { ns?: string; drawNo?: string }) => string,
): Promise<{ id: number; draw_no: string }> {
if (filters.drawId && filters.drawNo.trim()) {
return { id: filters.drawId, draw_no: filters.drawNo.trim() };
}
const drawNo = filters.drawNo.trim();
if (!drawNo) {
throw new LotteryApiBizError("请输入期号", -1, null);
throw new LotteryApiBizError(t("validation.drawNoRequired", { ns: "reports" }), -1, null);
}
const data = await getAdminDraws({ draw_no: drawNo, page: 1, per_page: 1 });
const matched = data.items.find((item) => item.draw_no === drawNo) ?? data.items[0];
if (!matched) {
throw new LotteryApiBizError("未找到期号", -1, { drawNo });
throw new LotteryApiBizError(t("validation.drawNoNotFound", { ns: "reports", drawNo }), -1, {
drawNo,
});
}
return { id: matched.id, draw_no: matched.draw_no };
}
@@ -459,7 +464,7 @@ export function ReportsConsole() {
try {
switch (selectedReport.key) {
case "draw_profit": {
const draw = await resolveDraw(filters);
const draw = await resolveDraw(filters, t);
const summary = await getAdminDrawFinanceSummary(draw.id);
setResult({
key: "draw_profit",
@@ -580,7 +585,7 @@ export function ReportsConsole() {
if (!filters.number.trim()) {
throw new LotteryApiBizError(t("validation.drawNoNumberRequired"), -1, null);
}
const draw = await resolveDraw(filters);
const draw = await resolveDraw(filters, t);
const detail = await getAdminRiskPoolDetail(draw.id, filters.number.trim(), { page, per_page: perPage });
const rows: ExportRow[] = [
{
@@ -627,7 +632,7 @@ export function ReportsConsole() {
break;
}
case "sold_out_number": {
const draw = await resolveDraw(filters);
const draw = await resolveDraw(filters, t);
const payload = await getAdminRiskPools(draw.id, { page, per_page: perPage, sold_out_only: true, sort: "number_asc" });
const rows = payload.items.map((item) => ({
draw_id: payload.draw_id,
@@ -1022,22 +1027,22 @@ export function ReportsConsole() {
<TableRow>
<TableCell className="font-medium">{summary.draw_no}</TableCell>
<TableCell>{summary.draw_status}</TableCell>
<TableCell className="text-right">{summary.order_count}</TableCell>
<TableCell className="text-right">{summary.ticket_item_count}</TableCell>
<TableCell className="text-right">{formatPlainMoney(summary.total_bet_minor, summary.currency_code)}</TableCell>
<TableCell className="text-right">{formatPlainMoney(summary.total_payout_minor, summary.currency_code)}</TableCell>
<TableCell className="text-right">{formatPlainMoney(summary.approx_house_gross_minor, summary.currency_code)}</TableCell>
<TableCell className="text-center">{summary.order_count}</TableCell>
<TableCell className="text-center">{summary.ticket_item_count}</TableCell>
<TableCell className="text-center">{formatPlainMoney(summary.total_bet_minor, summary.currency_code)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(summary.total_payout_minor, summary.currency_code)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(summary.approx_house_gross_minor, summary.currency_code)}</TableCell>
<TableCell>{summary.settlement_batches.length}</TableCell>
</TableRow>
{summary.settlement_batches.map((batch) => (
<TableRow key={batch.id} className="bg-muted/15">
<TableCell>#{batch.id}</TableCell>
<TableCell>{batch.status}</TableCell>
<TableCell className="text-right">{batch.total_ticket_count}</TableCell>
<TableCell className="text-right">{batch.total_win_count}</TableCell>
<TableCell className="text-right">-</TableCell>
<TableCell className="text-right">{formatPlainMoney(batch.total_payout_amount, summary.currency_code)}</TableCell>
<TableCell className="text-right">{formatPlainMoney(batch.total_jackpot_payout_amount, summary.currency_code)}</TableCell>
<TableCell className="text-center">{batch.total_ticket_count}</TableCell>
<TableCell className="text-center">{batch.total_win_count}</TableCell>
<TableCell className="text-center">-</TableCell>
<TableCell className="text-center">{formatPlainMoney(batch.total_payout_amount, summary.currency_code)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(batch.total_jackpot_payout_amount, summary.currency_code)}</TableCell>
<TableCell>{formatTs(batch.finished_at)}</TableCell>
</TableRow>
))}
@@ -1052,7 +1057,7 @@ export function ReportsConsole() {
<TableCell>{optionText(item.username, item.nickname) || item.player_id}</TableCell>
<TableCell>{item.direction}</TableCell>
<TableCell>{item.status}</TableCell>
<TableCell className="text-right">{item.currency_code} {item.amount}</TableCell>
<TableCell className="text-center">{item.currency_code} {item.amount}</TableCell>
<TableCell>{item.external_ref_no || "-"}</TableCell>
<TableCell>{item.fail_reason || "-"}</TableCell>
<TableCell>{formatTs(item.created_at)}</TableCell>
@@ -1066,9 +1071,9 @@ export function ReportsConsole() {
<TableRow>
<TableCell className="font-medium">{result.raw.pool.normalized_number}</TableCell>
<TableCell>{result.raw.draw_no}</TableCell>
<TableCell className="text-right">{formatPlainMoney(result.raw.pool.total_cap_amount, result.raw.currency_code)}</TableCell>
<TableCell className="text-right">{formatPlainMoney(result.raw.pool.locked_amount, result.raw.currency_code)}</TableCell>
<TableCell className="text-right">{formatPlainMoney(result.raw.pool.remaining_amount, result.raw.currency_code)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(result.raw.pool.total_cap_amount, result.raw.currency_code)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(result.raw.pool.locked_amount, result.raw.currency_code)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(result.raw.pool.remaining_amount, result.raw.currency_code)}</TableCell>
<TableCell>{result.raw.pool.is_sold_out ? t("yes") : t("no")}</TableCell>
<TableCell>{result.raw.pool.usage_ratio == null ? "-" : `${result.raw.pool.usage_ratio}%`}</TableCell>
<TableCell>v{result.raw.pool.version}</TableCell>
@@ -1077,7 +1082,7 @@ export function ReportsConsole() {
<TableRow key={item.id} className="bg-muted/15">
<TableCell className="font-mono text-xs">#{item.id}</TableCell>
<TableCell>{item.action_type}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.amount, result.raw.currency_code)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.amount, result.raw.currency_code)}</TableCell>
<TableCell>{playCodeLabel(item.play_code)}</TableCell>
<TableCell>{item.ticket_no || "-"}</TableCell>
<TableCell>{item.player_id || "-"}</TableCell>
@@ -1094,9 +1099,9 @@ export function ReportsConsole() {
<TableRow key={item.normalized_number}>
<TableCell className="font-medium">{item.normalized_number}</TableCell>
<TableCell>{filters.drawNo}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_cap_amount, null)}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.locked_amount, null)}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.remaining_amount, null)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_cap_amount, null)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.locked_amount, null)}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.remaining_amount, null)}</TableCell>
<TableCell>{item.is_sold_out ? t("yes") : t("no")}</TableCell>
<TableCell>{item.usage_ratio == null ? "-" : `${item.usage_ratio}%`}</TableCell>
<TableCell>v{item.version}</TableCell>
@@ -1109,9 +1114,9 @@ export function ReportsConsole() {
<TableRow key={item.business_date}>
<TableCell className="font-medium">{item.business_date}</TableCell>
<TableCell>-</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_bet_minor, "NPR")}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_payout_minor, "NPR")}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.approx_house_gross_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_bet_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_payout_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.approx_house_gross_minor, "NPR")}</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
@@ -1124,9 +1129,9 @@ export function ReportsConsole() {
<TableRow key={item.player_id}>
<TableCell className="font-medium">{item.username}</TableCell>
<TableCell>ID {item.player_id}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_bet_minor, "NPR")}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_payout_minor, "NPR")}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.net_win_loss_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_bet_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_payout_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.net_win_loss_minor, "NPR")}</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
@@ -1139,9 +1144,9 @@ export function ReportsConsole() {
<TableRow key={`${item.play_code}-${item.dimension}`}>
<TableCell className="font-medium">{playCodeLabel(item.play_code)}</TableCell>
<TableCell>{item.dimension}D</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_bet_minor, "NPR")}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_payout_minor, "NPR")}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.approx_house_gross_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_bet_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_payout_minor, "NPR")}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.approx_house_gross_minor, "NPR")}</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
@@ -1154,8 +1159,8 @@ export function ReportsConsole() {
<TableRow key={item.play_code}>
<TableCell className="font-medium">{playCodeLabel(item.play_code)}</TableCell>
<TableCell>{item.order_count}</TableCell>
<TableCell className="text-right">{formatPlainMoney(item.total_rebate_minor, "NPR")}</TableCell>
<TableCell className="text-right">{item.ticket_item_count}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_rebate_minor, "NPR")}</TableCell>
<TableCell className="text-center">{item.ticket_item_count}</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
@@ -1294,9 +1299,9 @@ export function ReportsConsole() {
<TableRow>
<TableHead>{t("preview.columns.primary")}</TableHead>
<TableHead>{t("preview.columns.secondary")}</TableHead>
<TableHead className="text-right">{t("preview.columns.metricA")}</TableHead>
<TableHead className="text-right">{t("preview.columns.metricB")}</TableHead>
<TableHead className="text-right">{t("preview.columns.metricC")}</TableHead>
<TableHead className="text-center">{t("preview.columns.metricA")}</TableHead>
<TableHead className="text-center">{t("preview.columns.metricB")}</TableHead>
<TableHead className="text-center">{t("preview.columns.metricC")}</TableHead>
<TableHead>{t("preview.columns.status")}</TableHead>
<TableHead>{t("preview.columns.extra")}</TableHead>
<TableHead>{t("preview.columns.time")}</TableHead>