diff --git a/src/i18n/locales/en/config.json b/src/i18n/locales/en/config.json index a79eff8..f19b5a3 100644 --- a/src/i18n/locales/en/config.json +++ b/src/i18n/locales/en/config.json @@ -115,6 +115,7 @@ "autoSettlement": "Run settlement automatically", "autoApprove": "Auto-approve settlement batches", "autoPayout": "Auto-credit winnings to wallets", + "applyRebateToPayout": "Deduct rebate again on winning payouts", "playRulesHtml": "Play rules HTML (i18n)", "playRulesHtmlDesc": "Rendered on the player play-rules page per locale. Leave empty to fall back to another language or the default empty state." }, @@ -123,7 +124,8 @@ "cooldownMinutes": "How long to wait after publishing before entering settling. Use 0 to settle immediately.", "autoSettlement": "When disabled, tick will not run settlement automatically and admins must trigger it manually.", "autoApprove": "After cooldown ends and settlement completes, whether batches are automatically marked as approved.", - "autoPayout": "After a batch is approved, whether tick automatically credits winnings to player wallets." + "autoPayout": "After a batch is approved, whether tick automatically credits winnings to player wallets.", + "applyRebateToPayout": "When enabled, payout = gross win × (1 - rebate_rate_snapshot). Default off (rebate already reflected in actual deduct)." }, "states": { "enabled": "Enabled", @@ -333,9 +335,11 @@ "d4": "4D rebate rate (%)" }, "winEnjoy": { - "label": "Apply rebate on winning tickets", - "description": "Placeholder field. It can later be aligned with risk and settlement rules and persisted.", - "pendingNote": "Required by the product spec, but the API has no field yet. This row is informational only—you cannot change it here." + "label": "Deduct rebate on winning payouts", + "description": "Maps to settlement.apply_rebate_to_payout: when enabled, winning payout uses gross win × (1 - rebate_rate_snapshot).", + "hint": "Global switch; affects future settlement payouts immediately (not tied to odds version publish).", + "saveSuccess": "Winning-ticket rebate setting updated", + "saveFailed": "Update failed" }, "effectiveTime": "Effective time (current active odds version)" }, diff --git a/src/i18n/locales/en/reports.json b/src/i18n/locales/en/reports.json index 8aedae0..6c1da86 100644 --- a/src/i18n/locales/en/reports.json +++ b/src/i18n/locales/en/reports.json @@ -24,8 +24,11 @@ "csv": "CSV", "excel": "Excel", "csvServer": "Export CSV (full)", - "excelServer": "Export Excel (full)" + "excelServer": "Export Excel (full)", + "csvPreview": "Export preview CSV", + "excelPreview": "Export preview Excel" }, + "exportPreviewHint": "Exports only rows on the current preview page (pagination applies)", "tasks": { "refresh": "Refresh", "download": "Download", diff --git a/src/i18n/locales/ne/config.json b/src/i18n/locales/ne/config.json index 320e51a..7442fef 100644 --- a/src/i18n/locales/ne/config.json +++ b/src/i18n/locales/ne/config.json @@ -115,6 +115,7 @@ "autoSettlement": "सेटलमेन्ट स्वतः चलाउने", "autoApprove": "सेटलमेन्ट ब्याच स्वतः स्वीकृत", "autoPayout": "जित रकम स्वतः वालेटमा जम्मा", + "applyRebateToPayout": "जितेको टिकटको पेआउटमा पुनः रिबेट घटाउने", "playRulesHtml": "खेल नियम HTML (बहुभाषी)", "playRulesHtmlDesc": "खेलाडीको नियम पृष्ठमा भाषा अनुसार HTML देखिन्छ। खाली छोड्दा अर्को भाषा वा पूर्वनिर्धारित खाली सूचना देखिन्छ।" }, @@ -123,7 +124,8 @@ "cooldownMinutes": "प्रकाशनपछि settling मा जानुअघि कति समय पर्खने। 0 राखे तुरुन्त सेटलमेन्ट सुरु हुन्छ।", "autoSettlement": "बन्द हुँदा tick ले सेटलमेन्ट स्वतः चलाउँदैन र एडमिनले म्यानुअल रूपमा ट्रिगर गर्नुपर्छ।", "autoApprove": "कूलडाउन सकिएर सेटलमेन्ट पूरा भएपछि ब्याच स्वतः अनुमोदित हुने हो कि होइन।", - "autoPayout": "ब्याच अनुमोदित भएपछि tick ले जित रकम खेलाडीको वालेटमा स्वतः जम्मा गर्ने हो कि होइन।" + "autoPayout": "ब्याच अनुमोदित भएपछि tick ले जित रकम खेलाडीको वालेटमा स्वतः जम्मा गर्ने हो कि होइन।", + "applyRebateToPayout": "सक्रिय हुँदा पेआउट = gross win × (1 - rebate_rate_snapshot)। पूर्वनिर्धारित बन्द (रिबेट actual deduct मा पहिले नै समायोजित)।" }, "states": { "enabled": "सक्रिय", @@ -333,9 +335,11 @@ "d4": "4D रिबेट दर (%)" }, "winEnjoy": { - "label": "जितेका टिकटहरूमा पनि रिबेट लागू गर्ने", - "description": "यो placeholder field हो। पछि risk र settlement नियमसँग मिलाएर स्थायी रूपमा राख्न सकिन्छ।", - "pendingNote": "उत्पादन विनिर्देशनले यो switch चाहिन्छ, तर API मा field छैन। यहाँ केवल जानकारी देखाइन्छ—यहाँबाट बदल्न मिल्दैन।" + "label": "जितेको टिकटको पेआउटमा पुनः रिबेट घटाउने", + "description": "settlement.apply_rebate_to_payout सँग जोडिएको: सक्रिय हुँदा जित पेआउटमा rebate_rate_snapshot अनुसार घटाउँछ।", + "hint": "वैश्विक switch; odds संस्करण प्रकाशनसँग नजोडिएको, सुरक्षित गर्दा तुरुन्त लागू।", + "saveSuccess": "जित टिकट रिबेट सेटिङ अद्यावधिक भयो", + "saveFailed": "अद्यावधिक असफल" }, "effectiveTime": "लागू समय (हाल सक्रिय अड्स संस्करण)" }, diff --git a/src/i18n/locales/ne/reports.json b/src/i18n/locales/ne/reports.json index 710188a..82f1473 100644 --- a/src/i18n/locales/ne/reports.json +++ b/src/i18n/locales/ne/reports.json @@ -24,8 +24,11 @@ "csv": "CSV", "excel": "Excel", "csvServer": "CSV निर्यात (पूर्ण)", - "excelServer": "Excel निर्यात (पूर्ण)" + "excelServer": "Excel निर्यात (पूर्ण)", + "csvPreview": "पूर्वावलोकन CSV", + "excelPreview": "पूर्वावलोकन Excel" }, + "exportPreviewHint": "हालको पूर्वावलोकन पृष्ठका पङ्क्तिहरू मात्र (पेजिनेसन लागू)", "tasks": { "refresh": "रिफ्रेस", "download": "डाउनलोड", diff --git a/src/i18n/locales/zh/config.json b/src/i18n/locales/zh/config.json index ea3d9bf..f53990c 100644 --- a/src/i18n/locales/zh/config.json +++ b/src/i18n/locales/zh/config.json @@ -115,6 +115,7 @@ "autoSettlement": "自动执行结算", "autoApprove": "自动审核结算批次", "autoPayout": "自动派彩入账", + "applyRebateToPayout": "中奖注单结算时再扣回水", "playRulesHtml": "玩法规则 HTML(多语言)", "playRulesHtmlDesc": "该内容将直接在玩家端的玩法规则页面作为 HTML 渲染。按语言分别配置;留空则回退其它语言或显示默认提示。" }, @@ -123,7 +124,8 @@ "cooldownMinutes": "结果发布后等待多久再进入 settling。填 0 表示发布后直接进入结算。", "autoSettlement": "关闭后,tick 不会自动跑结算,只能由后台手工执行。", "autoApprove": "冷静期结束并跑完结算后,是否自动将批次标记为已审核。", - "autoPayout": "批次已审核后,是否由 tick 自动把中奖金额打入玩家钱包。" + "autoPayout": "批次已审核后,是否由 tick 自动把中奖金额打入玩家钱包。", + "applyRebateToPayout": "开启后派彩金额 = 毛赢 × (1 - 回水率快照)。默认关闭(下注实扣已体现回水)。" }, "states": { "enabled": "已开启", @@ -333,9 +335,11 @@ "d4": "4D 回水比例 (%)" }, "winEnjoy": { - "label": "中奖注单也应用回水", - "description": "这是预留字段,后续可和风控、结算规则对齐后再真正落库存储。", - "pendingNote": "产品要求支持该开关,但后端尚未提供配置字段;当前仅展示说明,无法在此修改。" + "label": "中奖注单结算时再扣回水", + "description": "对应系统参数 settlement.apply_rebate_to_payout:开启后中奖派彩在毛赢基础上再乘 (1 - 回水率快照)。", + "hint": "全局开关,保存后立即影响后续结算派彩,不随赔率版本发布。", + "saveSuccess": "已更新中奖回水结算开关", + "saveFailed": "更新失败" }, "effectiveTime": "生效时间(当前赔率生效版本)" }, diff --git a/src/i18n/locales/zh/reports.json b/src/i18n/locales/zh/reports.json index a9eac55..1158b0b 100644 --- a/src/i18n/locales/zh/reports.json +++ b/src/i18n/locales/zh/reports.json @@ -24,8 +24,11 @@ "csv": "CSV", "excel": "Excel", "csvServer": "导出 CSV(全量)", - "excelServer": "导出 Excel(全量)" + "excelServer": "导出 Excel(全量)", + "csvPreview": "导出当前页 CSV", + "excelPreview": "导出当前页 Excel" }, + "exportPreviewHint": "仅导出当前预览表格中的数据(受分页限制)", "tasks": { "refresh": "刷新", "download": "下载", diff --git a/src/modules/account/account-settings-console.tsx b/src/modules/account/account-settings-console.tsx index c621d63..542ca69 100644 --- a/src/modules/account/account-settings-console.tsx +++ b/src/modules/account/account-settings-console.tsx @@ -25,9 +25,11 @@ export function AccountSettingsConsole() { const [loading, setLoading] = useState(false); useEffect(() => { - if (adminProfile) { - setNickname(adminProfile.nickname ?? ""); - } + queueMicrotask(() => { + if (adminProfile) { + setNickname(adminProfile.nickname ?? ""); + } + }); }, [adminProfile]); async function handleUpdateProfile() { diff --git a/src/modules/config/config-status-badge.tsx b/src/modules/config/config-status-badge.tsx index 0845b52..04952be 100644 --- a/src/modules/config/config-status-badge.tsx +++ b/src/modules/config/config-status-badge.tsx @@ -3,12 +3,18 @@ import { useTranslation } from "react-i18next"; import { AdminStatusBadge } from "@/components/admin/admin-status-badge"; import { resolveAdminStatusTone } from "@/lib/admin-status-tone"; -export function ConfigStatusBadge({ status }: { status: string }) { +export function ConfigStatusBadge({ + status, + className, +}: { + status: string; + className?: string; +}) { const { t } = useTranslation("config"); const label = t(`versionStatus.${status}`, { defaultValue: status }); return ( - + {label} ); diff --git a/src/modules/config/doc/odds-config-doc-screen.tsx b/src/modules/config/doc/odds-config-doc-screen.tsx index a2b843d..755eb54 100644 --- a/src/modules/config/doc/odds-config-doc-screen.tsx +++ b/src/modules/config/doc/odds-config-doc-screen.tsx @@ -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({ - {t("odds.rollbackDialog.title", { ns: "config" })} + {t("versionActions.rollbackDialog.title", { ns: "config" })} - {t("odds.rollbackDialog.description", { ns: "config", version: rollbackTarget?.version_no ?? "—" })} + {t("versionActions.rollbackDialog.description", { + ns: "config", + version: rollbackTarget?.version_no ?? "—", + })} @@ -621,7 +624,7 @@ export function OddsConfigDocScreen({ {t("actions.cancel", { ns: "adminUsers" })} diff --git a/src/modules/config/doc/rebate-config-doc-screen.tsx b/src/modules/config/doc/rebate-config-doc-screen.tsx index a5ad980..7ef8224 100644 --- a/src/modules/config/doc/rebate-config-doc-screen.tsx +++ b/src/modules/config/doc/rebate-config-doc-screen.tsx @@ -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([]); const [listRows, setListRows] = useState([]); @@ -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 { + 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({ - - - {t("rebate.winEnjoy.label", { ns: "config" })} - {" — "} - {t("rebate.winEnjoy.pendingNote", { ns: "config" })} - - +
+
+
+

{t("rebate.winEnjoy.label", { ns: "config" })}

+

{t("rebate.winEnjoy.description", { ns: "config" })}

+
+ void handleWinEnjoyChange(value)} + /> +
+

{t("rebate.winEnjoy.hint", { ns: "config" })}

+
{!embedded ? (
diff --git a/src/modules/config/risk-cap-runtime-panel.tsx b/src/modules/config/risk-cap-runtime-panel.tsx index 7a643d3..deca3fa 100644 --- a/src/modules/config/risk-cap-runtime-panel.tsx +++ b/src/modules/config/risk-cap-runtime-panel.tsx @@ -102,11 +102,15 @@ export function RiskCapRuntimePanel() { }, [appliedNumber, drawId, poolFilter, t]); useEffect(() => { - void loadDraws(); + queueMicrotask(() => { + void loadDraws(); + }); }, [loadDraws]); useEffect(() => { - void loadPools(); + queueMicrotask(() => { + void loadPools(); + }); }, [loadPools]); const riskBase = drawId ? `/admin/draws/${drawId}/risk` : null; diff --git a/src/modules/dashboard/dashboard-console.tsx b/src/modules/dashboard/dashboard-console.tsx index c2f342d..afc36ed 100644 --- a/src/modules/dashboard/dashboard-console.tsx +++ b/src/modules/dashboard/dashboard-console.tsx @@ -150,7 +150,9 @@ export function DashboardConsole(): ReactElement { }, [i18n.language]); useEffect(() => { - void loadPlayOptions(); + queueMicrotask(() => { + void loadPlayOptions(); + }); }, [loadPlayOptions]); const load = useCallback(async (isRefresh = false) => { diff --git a/src/modules/draws/draw-create-dialog.tsx b/src/modules/draws/draw-create-dialog.tsx index d994473..02d42ba 100644 --- a/src/modules/draws/draw-create-dialog.tsx +++ b/src/modules/draws/draw-create-dialog.tsx @@ -47,9 +47,11 @@ export function DrawCreateDialog({ const [saving, setSaving] = useState(false); useEffect(() => { - if (!open) { - setForm(resetFormState()); - } + queueMicrotask(() => { + if (!open) { + setForm(resetFormState()); + } + }); }, [open]); async function submit(): Promise { diff --git a/src/modules/draws/draw-edit-dialog.tsx b/src/modules/draws/draw-edit-dialog.tsx index 02842d0..db490bf 100644 --- a/src/modules/draws/draw-edit-dialog.tsx +++ b/src/modules/draws/draw-edit-dialog.tsx @@ -51,13 +51,15 @@ export function DrawEditDialog({ const [saving, setSaving] = useState(false); useEffect(() => { - if (!open || draw == null) { - return; - } - setDrawTime(isoToScheduleValue(draw.draw_time)); - setCloseTime(isoToScheduleValue(draw.close_time)); - setStartTime(isoToScheduleValue(draw.start_time)); - setDrawNo(draw.draw_no); + queueMicrotask(() => { + if (!open || draw == null) { + return; + } + setDrawTime(isoToScheduleValue(draw.draw_time)); + setCloseTime(isoToScheduleValue(draw.close_time)); + setStartTime(isoToScheduleValue(draw.start_time)); + setDrawNo(draw.draw_no); + }); }, [open, draw]); async function submit(): Promise { diff --git a/src/modules/reports/report-jobs-panel.tsx b/src/modules/reports/report-jobs-panel.tsx index 02884c7..fc38ac9 100644 --- a/src/modules/reports/report-jobs-panel.tsx +++ b/src/modules/reports/report-jobs-panel.tsx @@ -58,7 +58,9 @@ export function ReportJobsPanel({ canExport, refreshToken = 0 }: ReportJobsPanel }, [t]); useEffect(() => { - void loadJobs(); + queueMicrotask(() => { + void loadJobs(); + }); }, [loadJobs, refreshToken]); async function handleDownload(job: AdminReportJobRow): Promise { diff --git a/src/modules/reports/reports-console.tsx b/src/modules/reports/reports-console.tsx index 9b4d315..5f030cf 100644 --- a/src/modules/reports/reports-console.tsx +++ b/src/modules/reports/reports-console.tsx @@ -431,7 +431,9 @@ export function ReportsConsole() { }, [i18n.language]); useEffect(() => { - void loadPlayOptions(); + queueMicrotask(() => { + void loadPlayOptions(); + }); }, [loadPlayOptions]); const loadSearchOptions = useCallback(async (kind: SearchKind, query: string) => { @@ -764,14 +766,18 @@ export function ReportsConsole() { }, [canViewReports, filters, page, perPage, selectedReport, t]); useEffect(() => { - setResult(null); - setError(null); - setPage(1); + queueMicrotask(() => { + setResult(null); + setError(null); + setPage(1); + }); }, [selectedKey]); useEffect(() => { if (result && result.key === selectedReport.key && selectedReport.connected) { - void queryReport(); + queueMicrotask(() => { + void queryReport(); + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, perPage]); @@ -828,14 +834,10 @@ export function ReportsConsole() { } } - function exportReport(format: ExportFormat): void { + function exportPreview(format: ExportFormat): void { if (!canExportReports) { return; } - if (usesServerExport) { - void exportViaServer(format); - return; - } if (!result || result.rows.length === 0) { toast.info(t("empty")); return; @@ -851,6 +853,17 @@ export function ReportsConsole() { } } + function exportReport(format: ExportFormat): void { + if (!canExportReports) { + return; + } + if (usesServerExport) { + void exportViaServer(format); + return; + } + exportPreview(format); + } + const renderSearchPicker = (kind: SearchKind) => { const value = kind === "draw" ? filters.drawNo : kind === "player" ? filters.player : filters.operator; @@ -1327,39 +1340,52 @@ export function ReportsConsole() {
{t("preview.title")}
-
- {usesServerExport ? ( -

{t("exportServerHint")}

- ) : ( -

{t("exportClientHint")}

- )} -
+
+

{t("exportServerHint")}

+
+ {result && result.rows.length > 0 ? ( + <> +

{t("exportPreviewHint")}

+
+ + +
+ + ) : null}
diff --git a/src/modules/settings/system-settings-screen.tsx b/src/modules/settings/system-settings-screen.tsx index 6198649..0095971 100644 --- a/src/modules/settings/system-settings-screen.tsx +++ b/src/modules/settings/system-settings-screen.tsx @@ -29,6 +29,7 @@ const DRAW_KEYS = { AUTO_SETTLEMENT: "settlement.auto_run_on_tick", AUTO_APPROVE: "settlement.auto_approve_on_tick", AUTO_PAYOUT: "settlement.auto_payout_on_tick", + APPLY_REBATE_TO_PAYOUT: "settlement.apply_rebate_to_payout", } as const; const FRONTEND_GROUP = "frontend"; @@ -45,6 +46,7 @@ interface RuntimeDraft { autoSettlement: boolean; autoApprove: boolean; autoPayout: boolean; + applyRebateToPayout: boolean; playRulesHtmlZh: string; playRulesHtmlEn: string; playRulesHtmlNe: string; @@ -92,6 +94,7 @@ export function SystemSettingsScreen() { autoSettlement: true, autoApprove: true, autoPayout: true, + applyRebateToPayout: false, playRulesHtmlZh: "", playRulesHtmlEn: "", playRulesHtmlNe: "", @@ -102,6 +105,7 @@ export function SystemSettingsScreen() { autoSettlement: true, autoApprove: true, autoPayout: true, + applyRebateToPayout: false, playRulesHtmlZh: "", playRulesHtmlEn: "", playRulesHtmlNe: "", @@ -131,6 +135,7 @@ export function SystemSettingsScreen() { autoSettlement: Boolean(kv[DRAW_KEYS.AUTO_SETTLEMENT] ?? true), autoApprove: Boolean(kv[DRAW_KEYS.AUTO_APPROVE] ?? true), autoPayout: Boolean(kv[DRAW_KEYS.AUTO_PAYOUT] ?? true), + applyRebateToPayout: Boolean(kv[DRAW_KEYS.APPLY_REBATE_TO_PAYOUT] ?? false), playRulesHtmlZh: String(kv[FRONTEND_KEYS.PLAY_RULES_HTML_ZH] ?? legacyHtml), playRulesHtmlEn: String(kv[FRONTEND_KEYS.PLAY_RULES_HTML_EN] ?? ""), playRulesHtmlNe: String(kv[FRONTEND_KEYS.PLAY_RULES_HTML_NE] ?? ""), @@ -167,6 +172,7 @@ export function SystemSettingsScreen() { await updateAdminSetting(DRAW_KEYS.AUTO_SETTLEMENT, draft.autoSettlement); await updateAdminSetting(DRAW_KEYS.AUTO_APPROVE, draft.autoApprove); await updateAdminSetting(DRAW_KEYS.AUTO_PAYOUT, draft.autoPayout); + await updateAdminSetting(DRAW_KEYS.APPLY_REBATE_TO_PAYOUT, draft.applyRebateToPayout); await updateAdminSetting(FRONTEND_KEYS.PLAY_RULES_HTML_ZH, draft.playRulesHtmlZh); await updateAdminSetting(FRONTEND_KEYS.PLAY_RULES_HTML_EN, draft.playRulesHtmlEn); await updateAdminSetting(FRONTEND_KEYS.PLAY_RULES_HTML_NE, draft.playRulesHtmlNe); @@ -242,6 +248,21 @@ export function SystemSettingsScreen() {
+
+
+ +

{t("system.hints.applyRebateToPayout", { ns: "config" })}

+
+ updateDraft("applyRebateToPayout", value)} + /> +
+ +
+