feat(i18n): add saveFailed message and dimensionRatesMixedHint to locale files
- Updated English, Nepali, and Chinese locale files to include a new message for "Failed to save configuration". - Added a hint for mixed rebate rates within the same dimension to enhance user understanding. - Improved internationalization support across multiple locales for better user experience in the admin interface.
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
ConfigVersionToolbarMetaEmphasis,
|
||||
} from "@/modules/config/config-version-toolbar-meta";
|
||||
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ConfigReadonlyValue } from "@/modules/config/config-readonly-value";
|
||||
@@ -28,6 +29,7 @@ import { ConfigVersionSwitcher } from "@/modules/config/config-version-switcher"
|
||||
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
||||
import { useConfirmAction } from "@/hooks/use-confirm-action";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { pickDefaultConfigVersionId } from "@/lib/config-version-auto-pick";
|
||||
import { PRD_REBATE_MANAGE } from "@/lib/admin-prd";
|
||||
import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
@@ -49,10 +51,38 @@ function rateToPercentUi(rateStr: string): string {
|
||||
}
|
||||
|
||||
function inferPercentFrom(dim: 2 | 3 | 4, rows: OddsItemRow[], typeList: AdminPlayTypeRow[]): string {
|
||||
const codes = typeList.filter((t) => (t.dimension ?? 2) === dim).map((t) => t.play_code);
|
||||
const codes = typeList
|
||||
.filter((t) => (t.dimension ?? 2) === dim)
|
||||
.map((t) => t.play_code)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
const scope = PRIZE_SCOPE_ORDER[0];
|
||||
const hit = rows.find((r) => codes.includes(r.play_code) && r.prize_scope === scope);
|
||||
return hit ? rateToPercentUi(String(hit.rebate_rate)) : "0";
|
||||
for (const code of codes) {
|
||||
const hit = rows.find((r) => r.play_code === code && r.prize_scope === scope);
|
||||
if (hit) {
|
||||
return rateToPercentUi(String(hit.rebate_rate));
|
||||
}
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
|
||||
function dimensionDistinctPrimaryScopePercents(
|
||||
dim: 2 | 3 | 4,
|
||||
rows: OddsItemRow[],
|
||||
typeList: AdminPlayTypeRow[],
|
||||
): Set<string> {
|
||||
const codes = typeList
|
||||
.filter((t) => (t.dimension ?? 2) === dim)
|
||||
.map((t) => t.play_code)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
const scope = PRIZE_SCOPE_ORDER[0];
|
||||
const percents = new Set<string>();
|
||||
for (const code of codes) {
|
||||
const hit = rows.find((r) => r.play_code === code && r.prize_scope === scope);
|
||||
if (hit) {
|
||||
percents.add(rateToPercentUi(String(hit.rebate_rate)));
|
||||
}
|
||||
}
|
||||
return percents;
|
||||
}
|
||||
|
||||
type RebateConfigDocScreenProps = {
|
||||
@@ -139,18 +169,26 @@ export function RebateConfigDocScreen({
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (listRows.length === 0 || selectedId !== "") {
|
||||
if (listRows.length === 0) {
|
||||
if (selectedId !== "") {
|
||||
queueMicrotask(() => {
|
||||
setSelectedId("");
|
||||
setDetail(null);
|
||||
setDraftRows([]);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selectedId !== "" && listRows.some((x) => String(x.id) === selectedId)) {
|
||||
return;
|
||||
}
|
||||
queueMicrotask(() => {
|
||||
const drafts = listRows.filter((x) => x.status === "draft").sort((a, b) => b.id - a.id);
|
||||
const active = listRows.find((x) => x.status === "active");
|
||||
const pick = drafts[0] ?? active ?? [...listRows].sort((a, b) => b.id - a.id)[0];
|
||||
if (pick) {
|
||||
setSelectedId(String(pick.id));
|
||||
const pickId = pickDefaultConfigVersionId(listRows);
|
||||
if (pickId) {
|
||||
setSelectedId(pickId);
|
||||
}
|
||||
});
|
||||
}, [listRows, selectedId]);
|
||||
}, [listRows, selectedId, setSelectedId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedId === "") {
|
||||
@@ -173,6 +211,18 @@ export function RebateConfigDocScreen({
|
||||
return m;
|
||||
}, [types]);
|
||||
|
||||
const rebateBulkPercentsMixed = useMemo(() => {
|
||||
if (types.length === 0 || draftRows.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (const dim of [2, 3, 4] as const) {
|
||||
if (dimensionDistinctPrimaryScopePercents(dim, draftRows, types).size > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, [types, draftRows]);
|
||||
|
||||
const selectedVersionSummary = useMemo(
|
||||
() => listRows.find((x) => String(x.id) === selectedId) ?? null,
|
||||
[listRows, selectedId],
|
||||
@@ -223,7 +273,7 @@ export function RebateConfigDocScreen({
|
||||
toast.success(t("versionActions.saveDraft", { ns: "config" }));
|
||||
void refreshList();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("wallet.saveFailed", { ns: "config" }));
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("versionActions.saveFailed", { ns: "config" }));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -354,6 +404,11 @@ export function RebateConfigDocScreen({
|
||||
|
||||
const fieldsBlock = (
|
||||
<>
|
||||
{rebateBulkPercentsMixed ? (
|
||||
<Alert className="border-amber-500/35 bg-amber-500/10 text-amber-950 dark:text-amber-100">
|
||||
<AlertDescription>{t("rebate.dimensionRatesMixedHint", { ns: "config" })}</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
<div className="grid gap-5 sm:grid-cols-3">
|
||||
<div className="grid gap-2">
|
||||
<Label>{t("rebate.fields.d2", { ns: "config" })}</Label>
|
||||
|
||||
Reference in New Issue
Block a user