feat(api, agents, i18n): enhance settlement features and multi-language support
Added new types and API functions for settlement period summaries and credit ledgers, improving the management of agent settlements. Updated the admin console to reflect these changes, enhancing user experience with better navigation and data presentation. Additionally, expanded multi-language support by incorporating new translations in English, Nepali, and Chinese for settlement-related terms, ensuring consistency across the platform.
This commit is contained in:
158
src/modules/config/doc/odds-config-play-nav.tsx
Normal file
158
src/modules/config/doc/odds-config-play-nav.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ConfigChip, ConfigChipGroup } from "@/modules/config/config-chip-group";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
buildOddsPlayFilterGroups,
|
||||
filterOddsPlayTypesByCategory,
|
||||
type OddsCategoryTab,
|
||||
} from "@/modules/config/doc/odds-play-type-groups";
|
||||
import { resolveAdminPlayTypeDisplayName } from "@/lib/admin-play-types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { AdminPlayTypeRow } from "@/types/api/admin-config";
|
||||
|
||||
const PLAY_SELECT_NONE = "__none__";
|
||||
|
||||
type OddsConfigPlayNavProps = {
|
||||
catTab: OddsCategoryTab;
|
||||
onCatTabChange: (tab: OddsCategoryTab) => void;
|
||||
onPlayCodeChange: (code: string) => void;
|
||||
types: AdminPlayTypeRow[];
|
||||
resolvedPlayCode: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function OddsConfigPlayNav({
|
||||
catTab,
|
||||
onCatTabChange,
|
||||
onPlayCodeChange,
|
||||
types,
|
||||
resolvedPlayCode,
|
||||
className,
|
||||
}: OddsConfigPlayNavProps) {
|
||||
const { t, i18n } = useTranslation("config");
|
||||
|
||||
const catTabs = [
|
||||
{ id: "all" as const, label: t("odds.tabs.all") },
|
||||
{ id: "d4" as const, label: "4D" },
|
||||
{ id: "d3" as const, label: "3D" },
|
||||
{ id: "d2" as const, label: "2D" },
|
||||
];
|
||||
|
||||
const filteredTypes = filterOddsPlayTypesByCategory(catTab, types);
|
||||
const playGroups = buildOddsPlayFilterGroups(catTab, types);
|
||||
const playSelectValue = resolvedPlayCode || PLAY_SELECT_NONE;
|
||||
|
||||
const playLabel = (code: string): string => {
|
||||
const row = types.find((item) => item.play_code === code);
|
||||
return resolveAdminPlayTypeDisplayName(code, i18n.language, row);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-4", className)}>
|
||||
<ConfigChipGroup label={t("odds.category")}>
|
||||
{catTabs.map((tab) => (
|
||||
<ConfigChip
|
||||
key={tab.id}
|
||||
active={catTab === tab.id}
|
||||
onClick={() => onCatTabChange(tab.id)}
|
||||
>
|
||||
{tab.label}
|
||||
</ConfigChip>
|
||||
))}
|
||||
</ConfigChipGroup>
|
||||
|
||||
{/* 小屏:下拉快速切换玩法 */}
|
||||
<div className="space-y-1.5 lg:hidden">
|
||||
<p className="text-sm font-medium">{t("odds.playType")}</p>
|
||||
<Select
|
||||
modal={false}
|
||||
value={playSelectValue}
|
||||
onValueChange={(value) => {
|
||||
if (value != null && value !== PLAY_SELECT_NONE) {
|
||||
onPlayCodeChange(value);
|
||||
}
|
||||
}}
|
||||
disabled={filteredTypes.length === 0}
|
||||
>
|
||||
<SelectTrigger className="h-9 w-full">
|
||||
<SelectValue>
|
||||
{(v) => {
|
||||
const code = v == null || v === "" || v === PLAY_SELECT_NONE ? "" : String(v);
|
||||
return code ? playLabel(code) : t("odds.playSelectPlaceholder");
|
||||
}}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{playGroups.length > 0 ? (
|
||||
playGroups.map((group) => (
|
||||
<SelectGroup key={group.key}>
|
||||
<SelectLabel>{t(`odds.playGroups.${group.key}`)}</SelectLabel>
|
||||
{group.types.map((type) => (
|
||||
<SelectItem key={type.play_code} value={type.play_code}>
|
||||
{playLabel(type.play_code)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value={PLAY_SELECT_NONE} disabled>
|
||||
{t("odds.noPlayTypes")}
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 大屏:侧栏玩法列表,点选即切换 */}
|
||||
<nav className="hidden lg:block" aria-label={t("odds.playType")}>
|
||||
<p className="mb-2 text-sm font-medium">{t("odds.playType")}</p>
|
||||
{filteredTypes.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">{t("odds.noPlayTypes")}</p>
|
||||
) : (
|
||||
<div className="max-h-[min(28rem,calc(100vh-16rem))] space-y-4 overflow-y-auto pr-1">
|
||||
{playGroups.map((group) => (
|
||||
<div key={group.key} className="space-y-1">
|
||||
<p className="px-2 text-xs font-medium text-muted-foreground">
|
||||
{t(`odds.playGroups.${group.key}`)}
|
||||
</p>
|
||||
<ul className="space-y-0.5">
|
||||
{group.types.map((type) => {
|
||||
const active = resolvedPlayCode === type.play_code;
|
||||
return (
|
||||
<li key={type.play_code}>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"w-full rounded-md px-2.5 py-2 text-left text-sm transition-colors",
|
||||
active
|
||||
? "bg-primary font-medium text-primary-foreground shadow-sm"
|
||||
: "text-foreground hover:bg-muted/80",
|
||||
)}
|
||||
onClick={() => onPlayCodeChange(type.play_code)}
|
||||
aria-current={active ? "true" : undefined}
|
||||
>
|
||||
{playLabel(type.play_code)}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user