diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..9b8b44b --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,32 @@ +"use client" + +import { Switch as SwitchPrimitive } from "@base-ui/react/switch" + +import { cn } from "@/lib/utils" + +function Switch({ + className, + size = "default", + ...props +}: SwitchPrimitive.Root.Props & { + size?: "sm" | "default" +}) { + return ( + + + + ) +} + +export { Switch } diff --git a/src/i18n/locales/en/config.json b/src/i18n/locales/en/config.json index 5f2b924..51ba7a8 100644 --- a/src/i18n/locales/en/config.json +++ b/src/i18n/locales/en/config.json @@ -236,7 +236,8 @@ "readOnly": "Read only" }, "aria": { - "enablePlay": "Enable {{playCode}}" + "enablePlay": "Enable {{playCode}}", + "batchGroupSwitch": "Toggle batch switch for {{group}}" }, "nameDialog": { "title": "Edit display name", diff --git a/src/i18n/locales/ne/config.json b/src/i18n/locales/ne/config.json index ba89bf9..c45c332 100644 --- a/src/i18n/locales/ne/config.json +++ b/src/i18n/locales/ne/config.json @@ -236,7 +236,8 @@ "readOnly": "केवल पढ्न मिल्ने" }, "aria": { - "enablePlay": "{{playCode}} सक्रिय गर्ने" + "enablePlay": "{{playCode}} सक्रिय गर्ने", + "batchGroupSwitch": "«{{group}}» समूह स्विच टगल गर्नुहोस्" }, "nameDialog": { "title": "प्रदर्शित नाम (बहुभाषी)", diff --git a/src/i18n/locales/zh/config.json b/src/i18n/locales/zh/config.json index 357a6ad..553e9eb 100644 --- a/src/i18n/locales/zh/config.json +++ b/src/i18n/locales/zh/config.json @@ -236,7 +236,8 @@ "readOnly": "只读" }, "aria": { - "enablePlay": "切换 {{playCode}} 启用状态" + "enablePlay": "切换 {{playCode}} 启用状态", + "batchGroupSwitch": "切换「{{group}}」批量开关" }, "nameDialog": { "title": "编辑显示名称", diff --git a/src/lib/money.ts b/src/lib/money.ts index e148a1b..16621d5 100644 --- a/src/lib/money.ts +++ b/src/lib/money.ts @@ -33,3 +33,37 @@ export function formatAdminMinorUnits( maximumFractionDigits: resolvedDecimalPlaces, })}`; } + +export function formatAdminMinorDecimal( + minor: number, + currencyCode = "NPR", + decimalPlaces?: number, +): string { + const resolvedDecimalPlaces = + typeof decimalPlaces === "number" && Number.isFinite(decimalPlaces) && decimalPlaces >= 0 + ? decimalPlaces + : getAdminCurrencyDecimalPlaces(currencyCode); + const major = minor / 10 ** resolvedDecimalPlaces; + return major.toLocaleString(undefined, { + minimumFractionDigits: resolvedDecimalPlaces, + maximumFractionDigits: resolvedDecimalPlaces, + }); +} + +export function parseAdminMajorToMinor( + raw: string, + currencyCode = "NPR", + decimalPlaces?: number, +): number | null { + const resolvedDecimalPlaces = + typeof decimalPlaces === "number" && Number.isFinite(decimalPlaces) && decimalPlaces >= 0 + ? decimalPlaces + : getAdminCurrencyDecimalPlaces(currencyCode); + const cleaned = raw.replace(/,/g, "").trim(); + if (!cleaned) return null; + const n = Number(cleaned); + if (!Number.isFinite(n) || n < 0) return null; + const factor = 10 ** resolvedDecimalPlaces; + const minor = Math.round(n * factor); + return Number.isSafeInteger(minor) ? minor : null; +} diff --git a/src/modules/admin-roles/admin-roles-console.tsx b/src/modules/admin-roles/admin-roles-console.tsx index 396d6fa..6e7212a 100644 --- a/src/modules/admin-roles/admin-roles-console.tsx +++ b/src/modules/admin-roles/admin-roles-console.tsx @@ -15,11 +15,10 @@ import { putAdminRole, putAdminRolePermissions, } from "@/api/admin-users"; -import { AdminStatusBadge } from "@/components/admin/admin-status-badge"; import { Badge } from "@/components/ui/badge"; -import { resolveRoleStatusTone } from "@/lib/admin-status-tone"; import { AdminTableExportButton } from "@/components/admin/admin-table-export-button"; import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { @@ -93,12 +92,6 @@ export function AdminRolesConsole(): React.ReactElement { [draftRolePermissions], ); - const selectClassName = cn( - "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors", - "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 md:text-sm", - "dark:bg-input/30 disabled:cursor-not-allowed disabled:opacity-50", - ); - const directPermissionGroups = useMemo(() => { const groups = catalog?.permission_menu_groups; if (groups && groups.length > 0) { @@ -376,9 +369,13 @@ export function AdminRolesConsole(): React.ReactElement { )} - - {role.status === 1 ? t("status.enabled") : t("status.disabled")} - +
+ +
{role.user_count} {role.permission_slugs.length} @@ -554,12 +551,19 @@ export function AdminRolesConsole(): React.ReactElement {
{t("roleDialog.descriptionLabel")}
setRoleDescription(e.target.value)} /> -
-
{t("roleDialog.status")}
- +
+
+

{t("roleDialog.status")}

+

+ {roleStatus === 1 ? t("status.enabled") : t("status.disabled")} +

+
+ setRoleStatus(checked ? 1 : 0)} + />
diff --git a/src/modules/admin-users/admin-users-console.tsx b/src/modules/admin-users/admin-users-console.tsx index 2e7aaa6..eaf0848 100644 --- a/src/modules/admin-users/admin-users-console.tsx +++ b/src/modules/admin-users/admin-users-console.tsx @@ -16,10 +16,9 @@ import { } from "@/api/admin-users"; import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer"; import { AdminTableExportButton } from "@/components/admin/admin-table-export-button"; -import { AdminStatusBadge } from "@/components/admin/admin-status-badge"; import { Badge } from "@/components/ui/badge"; -import { resolveAdminUserStatusTone } from "@/lib/admin-status-tone"; import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { @@ -92,12 +91,6 @@ export function AdminUsersConsole(): React.ReactElement { [catalog], ); - const selectClassName = cn( - "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors", - "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 md:text-sm", - "dark:bg-input/30 disabled:cursor-not-allowed disabled:opacity-50", - ); - const load = useCallback(async () => { setLoading(true); setErr(null); @@ -398,9 +391,13 @@ export function AdminUsersConsole(): React.ReactElement { {row.nickname ?? ""} - - {row.status === 0 ? t("status.enabled") : t("status.disabled")} - +
+ +
@@ -634,16 +631,19 @@ export function AdminUsersConsole(): React.ReactElement {
) : null} -
-
{t("table.status")}
- +
+
+

{t("table.status")}

+

+ {formStatus === 0 ? t("status.enabled") : t("status.disabled")} +

+
+ setFormStatus(checked ? 0 : 1)} + />
diff --git a/src/modules/config/doc/odds-config-doc-screen.tsx b/src/modules/config/doc/odds-config-doc-screen.tsx index c3da3a6..6b8ea59 100644 --- a/src/modules/config/doc/odds-config-doc-screen.tsx +++ b/src/modules/config/doc/odds-config-doc-screen.tsx @@ -57,6 +57,15 @@ function oddsMultiplierLabel(oddsValue: number): string { return (oddsValue / 10000).toFixed(4); } +function parseOddsMultiplierInput(raw: string): number { + const n = Number.parseFloat(raw); + if (!Number.isFinite(n) || n < 0) { + return 0; + } + const scaled = Math.round(n * 10000); + return Number.isSafeInteger(scaled) ? scaled : 0; +} + function filterTypes(tab: CatTab, types: AdminPlayTypeRow[]): AdminPlayTypeRow[] { if (tab === "all") { return types; @@ -515,19 +524,19 @@ export function OddsConfigDocScreen({ embedded = false }: OddsConfigDocScreenPro {canEditDraft ? ( updateOddsForScope(scope, { - odds_value: Number.parseInt(e.target.value, 10) || 0, + odds_value: parseOddsMultiplierInput(e.target.value), }) } /> ) : ( - {row.odds_value} + {oddsMultiplierLabel(row.odds_value)} )} {!embedded ? ( @@ -610,9 +619,11 @@ export function OddsConfigDocScreen({ embedded = false }: OddsConfigDocScreenPro
{row.label} - {row.oldValue === null ? "—" : row.oldValue} + {row.oldValue === null ? "—" : oddsMultiplierLabel(row.oldValue)} + + + {row.newValue === null ? "—" : oddsMultiplierLabel(row.newValue)} - {row.newValue ?? "—"}
))}
diff --git a/src/modules/config/doc/play-config-doc-screen.tsx b/src/modules/config/doc/play-config-doc-screen.tsx index 0ece77a..8b9f7ea 100644 --- a/src/modules/config/doc/play-config-doc-screen.tsx +++ b/src/modules/config/doc/play-config-doc-screen.tsx @@ -19,7 +19,7 @@ import { ConfigChipGroup } from "@/modules/config/config-chip-group"; import { ConfigContextBanner, ConfigContextEmphasis } from "@/modules/config/config-context-banner"; import { ConfigDocPage, ConfigDocToolbar } from "@/modules/config/config-doc-page"; import { ConfigSection } from "@/modules/config/config-section"; -import { Checkbox } from "@/components/ui/checkbox"; +import { Switch } from "@/components/ui/switch"; import { Dialog, DialogContent, @@ -29,7 +29,6 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Table, TableBody, @@ -46,6 +45,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 { formatAdminMinorDecimal, parseAdminMajorToMinor } from "@/lib/money"; import { PRD_PLAY_SWITCH_MANAGE } from "@/lib/admin-prd"; import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; @@ -151,9 +151,6 @@ export function PlayConfigDocScreen() { const [error, setError] = useState(null); const detailRequestSeq = useRef(0); - const [nameDialogOpen, setNameDialogOpen] = useState(false); - const [namePlayCode, setNamePlayCode] = useState(null); - const [nameDraft, setNameDraft] = useState(""); const [ruleDialogOpen, setRuleDialogOpen] = useState(false); const [rulePlayCode, setRulePlayCode] = useState(null); const [ruleDraftZh, setRuleDraftZh] = useState(""); @@ -258,6 +255,7 @@ export function PlayConfigDocScreen() { const isSelectedDetail = detail !== null && String(detail.id) === selectedId; const selectedStatus = isSelectedDetail ? detail.status : selectedVersionSummary?.status; const isDraft = selectedStatus === "draft"; + const amountCurrencyCode = "NPR"; const orderedRows = useMemo( () => @@ -373,30 +371,6 @@ export function PlayConfigDocScreen() { } } - function openNameEditor(play_code: string) { - const item = draftRows.find((row) => row.play_code === play_code); - setNamePlayCode(play_code); - setNameDraft(item?.display_name ?? item?.play_code ?? ""); - setNameDialogOpen(true); - } - - function saveNameDraft() { - if (!namePlayCode) { - return; - } - const name = nameDraft.trim(); - if (!name) { - toast.error(t("play.validation.displayNameRequired", { ns: "config" })); - return; - } - updateConfigRow(namePlayCode, { - display_name: name, - }); - setNameDialogOpen(false); - setNamePlayCode(null); - toast.message(t("play.nameDialog.savedLocal", { ns: "config" })); - } - function openRuleEditor(play_code: string) { const item = draftRows.find((row) => row.play_code === play_code); setRulePlayCode(play_code); @@ -501,52 +475,53 @@ export function PlayConfigDocScreen() { description={!isDraft ? t("play.readOnlyDraftHint", { ns: "config" }) : undefined} > - {batchSwitchStates.map((group) => ( -
-
-

{group.label}

-

- {group.total > 0 - ? t("play.batchEnabledCount", { - ns: "config", - enabledCount: group.enabledCount, - total: group.total, - }) - : t("play.noPlayTypes", { ns: "config" })} -

-
- -
- ))} +
+

{group.label}

+

+ {group.total > 0 + ? t("play.batchEnabledCount", { + ns: "config", + enabledCount: group.enabledCount, + total: group.total, + }) + : t("play.noPlayTypes", { ns: "config" })} +

+
+ { + const enable = checked; + const action = enable + ? t("play.batchSwitchEnable", { ns: "config" }) + : t("play.batchSwitchDisable", { ns: "config" }); + requestConfirm({ + title: t("play.batchSwitchConfirmTitle", { ns: "config", action }), + description: t("play.batchSwitchConfirmDescription", { + ns: "config", + action, + group: group.label, + count: group.total, + }), + confirmVariant: enable ? "default" : "destructive", + onConfirm: () => applyBatchSwitch(group, enable), + }); + }} + /> + + ); + })}
) : null} @@ -562,7 +537,7 @@ export function PlayConfigDocScreen() { {t("play.table.playCode", { ns: "config" })} {t("play.table.category", { ns: "config" })} {t("play.table.status", { ns: "config" })} - {t("play.table.displayName", { ns: "config" })} + {t("play.table.displayName", { ns: "config" })} {t("play.table.order", { ns: "config" })} {t("play.table.minBet", { ns: "config" })} {t("play.table.maxBet", { ns: "config" })} @@ -575,12 +550,16 @@ export function PlayConfigDocScreen() { {row.play_code} {row.category ?? "—"} - {isDraft ? ( - + { - const enabled = v === true; + disabled={!isDraft || saving} + aria-label={t("play.aria.enablePlay", { ns: "config", playCode: row.play_code })} + onCheckedChange={(checked) => { + if (!isDraft) { + return; + } + const enabled = checked; const action = enabled ? t("play.toggleEnable", { ns: "config" }) : t("play.toggleDisable", { ns: "config" }); @@ -598,35 +577,27 @@ export function PlayConfigDocScreen() { }, }); }} - aria-label={t("play.aria.enablePlay", { ns: "config", playCode: row.play_code })} /> - ) : ( -
- - {row.is_enabled - ? t("play.states.enabled", { ns: "config" }) - : t("play.states.disabled", { ns: "config" })} - -
- )} +
- + {isDraft ? ( -
-

- {row.display_name ?? row.play_code} -

- -
+ + updateConfigRow(row.play_code, { display_name: e.target.value }) + } + onBlur={(e) => { + const trimmed = e.target.value.trim(); + updateConfigRow(row.play_code, { + display_name: trimmed || row.play_code, + }); + }} + /> ) : ( {renderDisplayNameReadonly(row)} @@ -658,19 +629,20 @@ export function PlayConfigDocScreen() { {isDraft ? ( updateConfigRow(row.play_code, { - min_bet_amount: Number.parseInt(e.target.value, 10) || 0, + min_bet_amount: + parseAdminMajorToMinor(e.target.value, amountCurrencyCode) ?? 0, }) } /> ) : ( - {row.min_bet_amount} + {formatAdminMinorDecimal(row.min_bet_amount, amountCurrencyCode)} )}
@@ -678,19 +650,20 @@ export function PlayConfigDocScreen() { {isDraft ? ( updateConfigRow(row.play_code, { - max_bet_amount: Number.parseInt(e.target.value, 10) || 0, + max_bet_amount: + parseAdminMajorToMinor(e.target.value, amountCurrencyCode) ?? 0, }) } /> ) : ( - {row.max_bet_amount} + {formatAdminMinorDecimal(row.max_bet_amount, amountCurrencyCode)} )}
@@ -716,33 +689,6 @@ export function PlayConfigDocScreen() { )} - - - - {t("play.nameDialog.title", { ns: "config" })} - - {t("play.nameDialog.description", { ns: "config", playCode: namePlayCode ?? "—" })} - - -
- - setNameDraft(e.target.value)} - /> -
- - - - -
-
- diff --git a/src/modules/config/doc/rebate-config-doc-screen.tsx b/src/modules/config/doc/rebate-config-doc-screen.tsx index f454ed1..fa6cf2e 100644 --- a/src/modules/config/doc/rebate-config-doc-screen.tsx +++ b/src/modules/config/doc/rebate-config-doc-screen.tsx @@ -16,7 +16,7 @@ import { } from "@/api/admin-config"; import { ConfigContextBanner, ConfigContextEmphasis } from "@/modules/config/config-context-banner"; import { ConfigDocPage, ConfigDocToolbar } from "@/modules/config/config-doc-page"; -import { Checkbox } from "@/components/ui/checkbox"; +import { Switch } from "@/components/ui/switch"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { ConfigReadonlyValue } from "@/modules/config/config-readonly-value"; @@ -40,9 +40,9 @@ import { PRIZE_SCOPE_ORDER } from "@/modules/config/doc/prize-scopes"; function rateToPercentUi(rateStr: string): string { const n = Number.parseFloat(rateStr); if (!Number.isFinite(n)) { - return "0"; + return "0.00"; } - return String(Math.round(n * 10000) / 100); + return (Math.round(n * 10000) / 100).toFixed(2); } function inferPercentFrom(dim: 2 | 3 | 4, rows: OddsItemRow[], typeList: AdminPlayTypeRow[]): string { @@ -391,19 +391,11 @@ export function RebateConfigDocScreen({ embedded = false }: RebateConfigDocScree -
- -
- -
+
+ +
{!embedded ? ( diff --git a/src/modules/config/doc/risk-cap-doc-screen.tsx b/src/modules/config/doc/risk-cap-doc-screen.tsx index 195199c..e98fd39 100644 --- a/src/modules/config/doc/risk-cap-doc-screen.tsx +++ b/src/modules/config/doc/risk-cap-doc-screen.tsx @@ -41,6 +41,7 @@ import { ConfigVersionActions } from "@/modules/config/config-version-actions"; import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; import { useConfirmAction } from "@/hooks/use-confirm-action"; import { adminHasAnyPermission } from "@/lib/admin-permissions"; +import { formatAdminMinorDecimal, parseAdminMajorToMinor } from "@/lib/money"; import { PRD_RISK_CAP_MANAGE, PRD_RISK_CAP_VIEW } from "@/lib/admin-prd"; import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; @@ -95,6 +96,7 @@ export function RiskCapDocScreen() { const [syncOpen, setSyncOpen] = useState(false); const [occSearch, setOccSearch] = useState(""); + const amountCurrencyCode = "NPR"; const refreshList = useCallback(async () => { setLoadingList(true); @@ -123,7 +125,7 @@ export function RiskCapDocScreen() { setDefaultCapStr(""); return; } - setDefaultCapStr(String(defaultRow.cap_amount)); + setDefaultCapStr(formatAdminMinorDecimal(defaultRow.cap_amount, amountCurrencyCode)); } const loadDetail = useCallback(async (id: number) => { @@ -299,7 +301,7 @@ export function RiskCapDocScreen() { } function applyDefaultCap() { - const n = Number.parseInt(defaultCapStr, 10); + const n = parseAdminMajorToMinor(defaultCapStr, amountCurrencyCode); if (!Number.isFinite(n) || n <= 0) { toast.error(t("riskCap.validation.enterValidCapAmount", { ns: "config" })); return; @@ -408,7 +410,7 @@ export function RiskCapDocScreen() { /> ) : ( - {defaultCapStr || "—"} + {defaultCapStr || formatAdminMinorDecimal(0, amountCurrencyCode)} )}
@@ -474,19 +476,22 @@ export function RiskCapDocScreen() { {canEditDraft ? ( updateRow(idx, { - cap_amount: Number.parseInt(e.target.value, 10) || 0, + cap_amount: + parseAdminMajorToMinor(e.target.value, amountCurrencyCode) ?? 0, }) } /> ) : ( - {r.cap_amount} + + {formatAdminMinorDecimal(r.cap_amount, amountCurrencyCode)} + )} diff --git a/src/modules/jackpot/jackpot-pools-console.tsx b/src/modules/jackpot/jackpot-pools-console.tsx index 169b239..cf3bed7 100644 --- a/src/modules/jackpot/jackpot-pools-console.tsx +++ b/src/modules/jackpot/jackpot-pools-console.tsx @@ -10,19 +10,14 @@ import { } from "@/api/admin-jackpot"; import { useConfirmAction } from "@/hooks/use-confirm-action"; import { adminHasAnyPermission } from "@/lib/admin-permissions"; +import { formatAdminMinorDecimal, parseAdminMajorToMinor } from "@/lib/money"; import { PRD_JACKPOT_MANAGE, PRD_JACKPOT_MANUAL_BURST } from "@/lib/admin-prd"; import { ModuleScaffold } from "@/components/admin/module-scaffold"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; import { Dialog, DialogContent, @@ -50,12 +45,12 @@ type Draft = { function toDraft(p: AdminJackpotPoolRow): Draft { return { - current_amount: String(p.current_amount), + current_amount: formatAdminMinorDecimal(p.current_amount, p.currency_code), contribution_rate: String(p.contribution_rate), - trigger_threshold: String(p.trigger_threshold), + trigger_threshold: formatAdminMinorDecimal(p.trigger_threshold, p.currency_code), payout_rate: String(p.payout_rate), force_trigger_draw_gap: String(p.force_trigger_draw_gap), - min_bet_amount: String(p.min_bet_amount), + min_bet_amount: formatAdminMinorDecimal(p.min_bet_amount, p.currency_code), combo_trigger_play_codes: p.combo_trigger_play_codes.join(","), status: String(p.status), manual_burst_draw_id: "", @@ -116,12 +111,12 @@ export function JackpotPoolsConsole({ embedded = false }: JackpotPoolsConsolePro setSavingId(p.id); try { await putAdminJackpotPool(p.id, { - current_amount: Number.parseInt(d.current_amount, 10), + current_amount: parseAdminMajorToMinor(d.current_amount, p.currency_code) ?? 0, contribution_rate: Number(d.contribution_rate), - trigger_threshold: Number.parseInt(d.trigger_threshold, 10), + trigger_threshold: parseAdminMajorToMinor(d.trigger_threshold, p.currency_code) ?? 0, payout_rate: Number(d.payout_rate), force_trigger_draw_gap: Number.parseInt(d.force_trigger_draw_gap, 10), - min_bet_amount: Number.parseInt(d.min_bet_amount, 10), + min_bet_amount: parseAdminMajorToMinor(d.min_bet_amount, p.currency_code) ?? 0, combo_trigger_play_codes: d.combo_trigger_play_codes .split(",") .map((v) => v.trim().toLowerCase()) @@ -240,20 +235,19 @@ export function JackpotPoolsConsole({ embedded = false }: JackpotPoolsConsolePro onChange={(e) => updateDraft(p.id, { combo_trigger_play_codes: e.target.value })} /> -
- - +
+ + + updateDraft(p.id, { status: checked ? "1" : "0" }) + } + />
{canManageJackpot ? ( diff --git a/src/modules/players/players-console.tsx b/src/modules/players/players-console.tsx index 2c64ccf..d800f9d 100644 --- a/src/modules/players/players-console.tsx +++ b/src/modules/players/players-console.tsx @@ -16,9 +16,8 @@ import { } from "@/api/admin-player"; import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer"; import { AdminTableExportButton } from "@/components/admin/admin-table-export-button"; -import { AdminStatusBadge } from "@/components/admin/admin-status-badge"; -import { resolvePlayerStatusTone } from "@/lib/admin-status-tone"; import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, @@ -377,9 +376,35 @@ export function PlayersConsole(): React.ReactElement { : "—"} - - {playerStatusLabelT(row.status, t)} - + {canFreezePlayers ? ( +
+ { + const name = row.username ?? row.site_player_id; + if (checked) { + requestConfirm({ + title: t("confirmUnfreezeTitle"), + description: t("confirmUnfreezeDescription", { name }), + onConfirm: () => toggleFreeze(row, false), + }); + return; + } + requestConfirm({ + title: t("confirmFreezeTitle"), + description: t("confirmFreezeDescription", { name }), + onConfirm: () => toggleFreeze(row, true), + }); + }} + /> +
+ ) : ( + + {playerStatusLabelT(row.status, t)} + + )}
{row.last_login_at @@ -395,42 +420,6 @@ export function PlayersConsole(): React.ReactElement { {canManagePlayers || canFreezePlayers ? (
- {canFreezePlayers && row.status === 0 ? ( - - ) : null} - {canFreezePlayers && row.status === 1 ? ( - - ) : null} {canManagePlayers ? ( <>
@@ -349,10 +351,11 @@ export function CurrencySettingsPanel() {

{t("currencies.form.bettable", { ns: "config" })}

{t("currencies.form.bettableHint", { ns: "config" })}

- updateForm("is_bettable", checked === true)} + onCheckedChange={(checked) => updateForm("is_bettable", checked)} disabled={saving || !form.is_enabled} + aria-label={t("currencies.form.bettable", { ns: "config" })} /> diff --git a/src/modules/settings/system-settings-screen.tsx b/src/modules/settings/system-settings-screen.tsx index d3d16a1..6198649 100644 --- a/src/modules/settings/system-settings-screen.tsx +++ b/src/modules/settings/system-settings-screen.tsx @@ -15,6 +15,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { LotteryApiBizError } from "@/types/api/errors"; @@ -49,45 +50,6 @@ interface RuntimeDraft { playRulesHtmlNe: string; } -function BinaryChoice({ - active, - disabled, - onChange, - leftLabel, - rightLabel, -}: { - active: boolean; - disabled: boolean; - onChange: (value: boolean) => void; - leftLabel: string; - rightLabel: string; -}) { - return ( -
- - -
- ); -} - function SaveActions({ dirty, loading, @@ -234,12 +196,11 @@ export function SystemSettingsScreen() {
- updateDraft("requireManualReview", value)} - leftLabel={t("system.states.disabled", { ns: "config" })} - rightLabel={t("system.states.enabled", { ns: "config" })} + aria-label={t("system.fields.manualReview", { ns: "config" })} + onCheckedChange={(value) => updateDraft("requireManualReview", value)} />
@@ -247,12 +208,11 @@ export function SystemSettingsScreen() {
- updateDraft("autoSettlement", value)} - leftLabel={t("system.states.disabled", { ns: "config" })} - rightLabel={t("system.states.enabled", { ns: "config" })} + aria-label={t("system.fields.autoSettlement", { ns: "config" })} + onCheckedChange={(value) => updateDraft("autoSettlement", value)} />
@@ -260,12 +220,11 @@ export function SystemSettingsScreen() {
- updateDraft("autoApprove", value)} - leftLabel={t("system.states.disabled", { ns: "config" })} - rightLabel={t("system.states.enabled", { ns: "config" })} + aria-label={t("system.fields.autoApprove", { ns: "config" })} + onCheckedChange={(value) => updateDraft("autoApprove", value)} />
@@ -273,12 +232,11 @@ export function SystemSettingsScreen() {
- updateDraft("autoPayout", value)} - leftLabel={t("system.states.disabled", { ns: "config" })} - rightLabel={t("system.states.enabled", { ns: "config" })} + aria-label={t("system.fields.autoPayout", { ns: "config" })} + onCheckedChange={(value) => updateDraft("autoPayout", value)} />