refactor: 优化配置与奖池页面多语言编辑及管理端列表布局

This commit is contained in:
2026-05-22 16:55:34 +08:00
parent 2d4a23968e
commit 7d01e5c47e
12 changed files with 901 additions and 599 deletions

View File

@@ -168,9 +168,11 @@ export function PlayerTicketsConsole(): React.ReactElement {
<CardTitle className="admin-list-title">{t("playerTicketQuery")}</CardTitle>
</CardHeader>
<CardContent className="admin-list-content">
<div className="grid gap-3 lg:grid-cols-2 xl:grid-cols-4">
<div className="grid gap-1.5">
<Label htmlFor="pt-player">{t("playerId")}</Label>
<div className="admin-list-toolbar">
<div className="admin-list-field min-w-[12rem] flex-1 sm:max-w-md">
<Label htmlFor="pt-player" className="sm:shrink-0">
{t("playerId")}
</Label>
<Input
id="pt-player"
className="font-mono"
@@ -181,18 +183,22 @@ export function PlayerTicketsConsole(): React.ReactElement {
}
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="pt-draw">{t("drawNoOptional")}</Label>
<div className="admin-list-field">
<Label htmlFor="pt-draw" className="sm:shrink-0">
{t("drawNoOptional")}
</Label>
<Input
id="pt-draw"
className="font-mono text-sm"
className="font-mono text-sm sm:w-44"
placeholder={t("drawNoPlaceholder")}
value={draft.drawNo}
onChange={(e) => setDraft((current) => ({ ...current, drawNo: e.target.value }))}
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="pt-number">{t("numberKeyword")}</Label>
<div className="admin-list-field min-w-[12rem] flex-1 sm:max-w-md">
<Label htmlFor="pt-number" className="sm:shrink-0">
{t("numberKeyword")}
</Label>
<Input
id="pt-number"
className="font-mono text-sm"
@@ -203,62 +209,67 @@ export function PlayerTicketsConsole(): React.ReactElement {
}
/>
</div>
<div className="grid gap-1.5">
<AdminDateRangeField
id="pt-date-range"
label={t("placedDateRange")}
from={draft.startDate}
to={draft.endDate}
onRangeChange={(range) =>
setDraft((current) => ({
...current,
startDate: range.from,
endDate: range.to,
}))
}
/>
<div className="admin-list-field">
<Label htmlFor="pt-date-range" className="sm:shrink-0">
{t("placedDateRange")}
</Label>
<div className="min-w-0 w-full sm:w-56">
<AdminDateRangeField
id="pt-date-range"
from={draft.startDate}
to={draft.endDate}
onRangeChange={(range) =>
setDraft((current) => ({
...current,
startDate: range.from,
endDate: range.to,
}))
}
/>
</div>
</div>
</div>
<div className="grid gap-2">
<div className="flex items-center justify-between gap-3">
<span className="text-sm font-medium leading-none">{t("statusFilterLabel")}</span>
<span className="text-muted-foreground text-xs">{t("statusHint")}</span>
<div className="admin-list-field">
<Label htmlFor="pt-status" className="sm:shrink-0">
{t("statusFilterLabel")}
</Label>
<DropdownMenu>
<DropdownMenuTrigger
id="pt-status"
title={t("statusHint")}
className="inline-flex h-8 w-full min-w-0 items-center justify-between rounded-md border border-border bg-card px-3 text-left text-sm font-normal shadow-sm outline-none transition-all hover:bg-accent focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 sm:w-44"
>
<span className="truncate">{ticketStatusSummary(draft.statuses, t)}</span>
<ChevronDown className="size-4 shrink-0 opacity-60" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
{TICKET_STATUS_OPTIONS.map((status) => (
<DropdownMenuCheckboxItem
key={status}
checked={draft.statuses.includes(status)}
onCheckedChange={(checked) => toggleStatus(status, checked === true)}
>
{ticketStatusText(status, t)}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu>
<DropdownMenuTrigger className="inline-flex h-11 w-full items-center justify-between rounded-md border border-border bg-card px-4 text-left text-sm font-normal text-primary shadow-sm outline-none transition-all hover:bg-accent hover:text-primary focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50">
<span className="truncate">{ticketStatusSummary(draft.statuses, t)}</span>
<ChevronDown className="size-4 shrink-0 opacity-60" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[min(28rem,calc(100vw-2rem))]">
{TICKET_STATUS_OPTIONS.map((status) => (
<DropdownMenuCheckboxItem
key={status}
checked={draft.statuses.includes(status)}
onCheckedChange={(checked) => toggleStatus(status, checked === true)}
>
{ticketStatusText(status, t)}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex flex-wrap gap-2">
<AdminTableExportButton
tableId="tickets-table"
filename={exportLabels.filename}
<div className="admin-list-actions">
<AdminTableExportButton
tableId="tickets-table"
filename={exportLabels.filename}
sheetName={exportLabels.sheetName}
/>
<Button type="button" size="sm" onClick={() => runSearch()}>
{t("query")}
</Button>
<Button type="button" size="sm" variant="outline" onClick={() => resetFilters()}>
{t("resetFilters")}
</Button>
<Button type="button" size="sm" variant="secondary" onClick={() => void load()}>
{t("refreshCurrentPage")}
</Button>
/>
<Button type="button" size="sm" onClick={() => runSearch()}>
{t("query")}
</Button>
<Button type="button" size="sm" variant="outline" onClick={() => resetFilters()}>
{t("resetFilters")}
</Button>
<Button type="button" size="sm" variant="secondary" onClick={() => void load()}>
{t("refreshCurrentPage")}
</Button>
</div>
</div>
{applied.playerQuery || applied.drawNo || applied.numberKeyword || applied.startDate || applied.endDate || applied.statuses.length > 0 ? (