feat: enhance ticket order detail and status display

- Updated ticket status display logic to handle "failed" status and improve "settled_win" condition.
- Refactored TicketOrderDetailScreen to utilize search parameters for dynamic navigation and grouping of tickets.
- Enhanced TicketOrdersListScreen to group ticket items and improve rendering of order details.
- Added new translations for order-related terms in multiple languages to support enhanced user experience.
This commit is contained in:
2026-05-25 16:02:23 +08:00
parent ca3a1db770
commit 3bcbf7d256
10 changed files with 446 additions and 30 deletions

View File

@@ -1,7 +1,8 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { getTicketItemDetail } from "@/api/ticket-items";
@@ -17,6 +18,7 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { orderGroupPath } from "@/features/orders/group-ticket-items";
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
@@ -67,6 +69,7 @@ type TicketItemDetailWithExtras = TicketItemDetailPayload & {
/** 界面文档 §4.8 注单详情 */
export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
const searchParams = useSearchParams();
const { t } = useTranslation("player");
const { activeCurrency } = useActivePlayerCurrency();
useCurrencyCatalog();
@@ -74,6 +77,27 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const fromGroupKey = useMemo(
() => (searchParams.get("fromGroup") ?? "").trim(),
[searchParams],
);
const groupTickets = useMemo(
() => (searchParams.get("tickets") ?? "").trim(),
[searchParams],
);
const backNav = useMemo(() => {
if (!fromGroupKey) {
return { href: "/orders", label: t("orders.title") };
}
const ticketNos = groupTickets
? groupTickets.split(",").map((s) => s.trim()).filter(Boolean)
: undefined;
return {
href: orderGroupPath(fromGroupKey, ticketNos),
label: t("orders.groupDetail"),
};
}, [fromGroupKey, groupTickets, t]);
const load = useCallback(async () => {
setLoading(true);
setError(null);
@@ -98,8 +122,8 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
return (
<PlayerPanel
title={t("orders.betDetail")}
backHref="/orders"
backLabel={t("orders.title")}
backHref={backNav.href}
backLabel={backNav.label}
>
<div className="space-y-3">
<Skeleton className="h-12 rounded-xl" />
@@ -113,8 +137,8 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
return (
<PlayerPanel
title={t("orders.betDetail")}
backHref="/orders"
backLabel={t("orders.title")}
backHref={backNav.href}
backLabel={backNav.label}
>
<div className="rounded-xl border border-red-200 bg-red-50 px-3 py-3 text-sm text-red-700">
<p>{error ?? t("orders.noData")}</p>
@@ -174,8 +198,8 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
return (
<PlayerPanel
title={t("orders.betDetail")}
backHref="/orders"
backLabel={t("orders.title")}
backHref={backNav.href}
backLabel={backNav.label}
>
<div className="flex flex-col gap-3">
<Card className="ring-0 border border-[#e8eef7] bg-white shadow-[0_8px_28px_rgba(15,23,42,0.05)]">
@@ -368,12 +392,12 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
</Link>
) : null}
<Link
href="/orders"
href={backNav.href}
className={cn(
"inline-flex h-11 min-w-[140px] flex-1 items-center justify-center rounded-xl border border-[#dce7f7] bg-white px-4 text-sm font-semibold text-[#07459f] transition-colors hover:bg-[#f1f6ff]",
)}
>
{t("orders.backToOrders")}
{fromGroupKey ? t("orders.backToGroup") : t("orders.backToOrders")}
</Link>
</div>
</div>