feat(i18n): enhance locale support for rebate settings and report exports
- Updated English, Nepali, and Chinese locale files to include new translations for the "apply rebate to payout" feature, enhancing clarity on its functionality. - Added new export options for previewing CSV and Excel files in reports, improving user experience with clearer export capabilities. - Enhanced internationalization support across multiple locales to ensure consistent messaging in the admin interface.
This commit is contained in:
@@ -375,7 +375,7 @@ export function OddsConfigDocScreen({
|
||||
clone_from_version_id: rollbackTarget.id,
|
||||
});
|
||||
toast.success(
|
||||
t("odds.rollbackSuccess", {
|
||||
t("versionActions.rollbackSuccess", {
|
||||
ns: "config",
|
||||
fromVersion: rollbackTarget.version_no,
|
||||
version: d.version_no,
|
||||
@@ -388,7 +388,7 @@ export function OddsConfigDocScreen({
|
||||
setRollbackOpen(false);
|
||||
setRollbackTarget(null);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.rollbackFailed", { ns: "config" }));
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("versionActions.rollbackFailed", { ns: "config" }));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -611,9 +611,12 @@ export function OddsConfigDocScreen({
|
||||
<Dialog open={rollbackOpen} onOpenChange={setRollbackOpen}>
|
||||
<DialogContent showCloseButton className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("odds.rollbackDialog.title", { ns: "config" })}</DialogTitle>
|
||||
<DialogTitle>{t("versionActions.rollbackDialog.title", { ns: "config" })}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("odds.rollbackDialog.description", { ns: "config", version: rollbackTarget?.version_no ?? "—" })}
|
||||
{t("versionActions.rollbackDialog.description", {
|
||||
ns: "config",
|
||||
version: rollbackTarget?.version_no ?? "—",
|
||||
})}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
@@ -621,7 +624,7 @@ export function OddsConfigDocScreen({
|
||||
{t("actions.cancel", { ns: "adminUsers" })}
|
||||
</Button>
|
||||
<Button type="button" onClick={() => void handleRollback()} disabled={!rollbackTarget || saving}>
|
||||
{t("odds.rollbackDialog.confirm", { ns: "config" })}
|
||||
{t("versionActions.rollbackDialog.confirm", { ns: "config" })}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -19,8 +19,10 @@ import {
|
||||
ConfigVersionToolbarMeta,
|
||||
ConfigVersionToolbarMetaEmphasis,
|
||||
} from "@/modules/config/config-version-toolbar-meta";
|
||||
import { getAdminSettings, updateAdminSetting } from "@/api/admin-settings";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -38,7 +40,7 @@ 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 { PRD_REBATE_MANAGE, PRD_WALLET_RECONCILE_MANAGE } from "@/lib/admin-prd";
|
||||
import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type {
|
||||
@@ -50,6 +52,9 @@ import type {
|
||||
|
||||
import { PRIZE_SCOPE_ORDER } from "@/modules/config/doc/prize-scopes";
|
||||
|
||||
const SETTLEMENT_GROUP = "settlement";
|
||||
const APPLY_REBATE_TO_PAYOUT_KEY = "settlement.apply_rebate_to_payout";
|
||||
|
||||
function rateToPercentUi(rateStr: string): string {
|
||||
const n = Number.parseFloat(rateStr);
|
||||
if (!Number.isFinite(n)) {
|
||||
@@ -108,6 +113,13 @@ export function RebateConfigDocScreen({
|
||||
const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
|
||||
const profile = useAdminProfile();
|
||||
const canManage = adminHasAnyPermission(profile?.permissions, [PRD_REBATE_MANAGE]);
|
||||
const canEditWinEnjoy = adminHasAnyPermission(profile?.permissions, [
|
||||
PRD_REBATE_MANAGE,
|
||||
PRD_WALLET_RECONCILE_MANAGE,
|
||||
]);
|
||||
const [applyRebateToPayout, setApplyRebateToPayout] = useState(false);
|
||||
const [winEnjoyLoading, setWinEnjoyLoading] = useState(true);
|
||||
const [winEnjoySaving, setWinEnjoySaving] = useState(false);
|
||||
const formatDt = useAdminDateTimeFormatter();
|
||||
const [types, setTypes] = useState<AdminPlayTypeRow[]>([]);
|
||||
const [listRows, setListRows] = useState<ConfigVersionSummary[]>([]);
|
||||
@@ -147,6 +159,19 @@ export function RebateConfigDocScreen({
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
const loadWinEnjoySetting = useCallback(async () => {
|
||||
setWinEnjoyLoading(true);
|
||||
try {
|
||||
const res = await getAdminSettings(SETTLEMENT_GROUP);
|
||||
const hit = res.items.find((item) => item.key === APPLY_REBATE_TO_PAYOUT_KEY);
|
||||
setApplyRebateToPayout(Boolean(hit?.value));
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("errors.loadFailed", { ns: "common" }));
|
||||
} finally {
|
||||
setWinEnjoyLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
queueMicrotask(async () => {
|
||||
setLoading(true);
|
||||
@@ -156,6 +181,28 @@ export function RebateConfigDocScreen({
|
||||
});
|
||||
}, [refreshTypes, refreshList]);
|
||||
|
||||
useEffect(() => {
|
||||
queueMicrotask(() => {
|
||||
void loadWinEnjoySetting();
|
||||
});
|
||||
}, [loadWinEnjoySetting]);
|
||||
|
||||
async function handleWinEnjoyChange(checked: boolean): Promise<void> {
|
||||
if (!canEditWinEnjoy) {
|
||||
return;
|
||||
}
|
||||
setWinEnjoySaving(true);
|
||||
try {
|
||||
await updateAdminSetting(APPLY_REBATE_TO_PAYOUT_KEY, checked);
|
||||
setApplyRebateToPayout(checked);
|
||||
toast.success(t("rebate.winEnjoy.saveSuccess", { ns: "config" }));
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("rebate.winEnjoy.saveFailed", { ns: "config" }));
|
||||
} finally {
|
||||
setWinEnjoySaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
const loadDetail = useCallback(async (id: number) => {
|
||||
setLoadingDetail(true);
|
||||
try {
|
||||
@@ -511,13 +558,21 @@ export function RebateConfigDocScreen({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Alert className="border-border/80 bg-muted/30">
|
||||
<AlertDescription className="text-sm leading-relaxed">
|
||||
<span className="font-medium text-foreground">{t("rebate.winEnjoy.label", { ns: "config" })}</span>
|
||||
{" — "}
|
||||
{t("rebate.winEnjoy.pendingNote", { ns: "config" })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<div className="rounded-xl border border-border/60 px-4 py-3">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<p className="text-sm font-medium">{t("rebate.winEnjoy.label", { ns: "config" })}</p>
|
||||
<p className="text-xs text-muted-foreground">{t("rebate.winEnjoy.description", { ns: "config" })}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={applyRebateToPayout}
|
||||
disabled={winEnjoyLoading || winEnjoySaving || !canEditWinEnjoy}
|
||||
aria-label={t("rebate.winEnjoy.label", { ns: "config" })}
|
||||
onCheckedChange={(value) => void handleWinEnjoyChange(value)}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-muted-foreground">{t("rebate.winEnjoy.hint", { ns: "config" })}</p>
|
||||
</div>
|
||||
|
||||
{!embedded ? (
|
||||
<div className="grid gap-1 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user