feat(admin, i18n): enhance admin dashboard and user management with new features and translations
Added the ability to filter admin dashboard data by site code and agent node ID, improving data retrieval capabilities. Introduced new functions for fetching dashboard data based on these parameters. Updated the admin users and roles management components to reflect these changes. Enhanced multi-language support by adding new translations for agent management and permission levels in English, Nepali, and Chinese, ensuring a consistent user experience across the admin interface.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import * as XLSX from "xlsx";
|
||||
@@ -46,7 +47,6 @@ import { getAdminTransferOrders } from "@/api/admin-wallet";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { PRD_REPORT_EXPORT, PRD_REPORT_VIEW } from "@/lib/admin-prd";
|
||||
import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { AdminAgentFilter } from "@/components/admin/admin-agent-filter";
|
||||
import { adminAgentDisplayLabel } from "@/components/admin/admin-agent-columns";
|
||||
import { AdminDateRangeField } from "@/components/admin/admin-date-range-field";
|
||||
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
||||
@@ -92,7 +92,7 @@ import type {
|
||||
AdminReportRebateCommissionRow,
|
||||
} from "@/types/api/admin-reports";
|
||||
|
||||
type ReportCategory = "profit" | "wallet" | "risk" | "audit";
|
||||
export type ReportCategory = "profit" | "wallet" | "risk" | "audit";
|
||||
type FilterKind = "draw" | "date" | "player_period" | "draw_number" | "play" | "play_period" | "operator_period";
|
||||
type FieldKey = "drawNo" | "number" | "player" | "play" | "operator" | "period";
|
||||
type ExportFormat = "csv" | "excel";
|
||||
@@ -138,7 +138,6 @@ type ReportFilters = {
|
||||
number: string;
|
||||
player: string;
|
||||
playerId: number | null;
|
||||
agentNodeId: number | undefined;
|
||||
play: string;
|
||||
operator: string;
|
||||
operatorId: number | null;
|
||||
@@ -202,7 +201,6 @@ const emptyFilters: ReportFilters = {
|
||||
number: "",
|
||||
player: "",
|
||||
playerId: null,
|
||||
agentNodeId: undefined,
|
||||
play: "",
|
||||
operator: "",
|
||||
operatorId: null,
|
||||
@@ -323,7 +321,11 @@ function optionText(...parts: Array<string | number | null | undefined>): string
|
||||
return parts.filter((part) => part !== null && part !== undefined && String(part).trim() !== "").join(" / ");
|
||||
}
|
||||
|
||||
function reportListParams(filters: ReportFilters, page: number, perPage: number) {
|
||||
function reportListParams(
|
||||
filters: ReportFilters,
|
||||
page: number,
|
||||
perPage: number,
|
||||
) {
|
||||
return {
|
||||
page,
|
||||
per_page: perPage,
|
||||
@@ -331,7 +333,6 @@ function reportListParams(filters: ReportFilters, page: number, perPage: number)
|
||||
date_to: filters.dateTo || undefined,
|
||||
player_id: filters.playerId ?? undefined,
|
||||
play_code: filters.play.trim() || undefined,
|
||||
agent_node_id: filters.agentNodeId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -401,7 +402,7 @@ function resultRowCount(result: ReportResult | null): number {
|
||||
return result?.rows.length ?? 0;
|
||||
}
|
||||
|
||||
export function ReportsConsole() {
|
||||
export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCategory } = {}) {
|
||||
const { t, i18n } = useTranslation(["reports", "common"]);
|
||||
const profile = useAdminProfile();
|
||||
const canViewReports = adminHasAnyPermission(profile?.permissions, [PRD_REPORT_VIEW]);
|
||||
@@ -410,7 +411,13 @@ export function ReportsConsole() {
|
||||
useAdminPlayTypeCatalog();
|
||||
const playCodeLabel = useAdminPlayCodeLabel();
|
||||
const formatTs = useAdminDateTimeFormatter();
|
||||
const [selectedKey, setSelectedKey] = useState<ReportKey>(REPORTS[0].key);
|
||||
const filteredReports = useMemo(
|
||||
() => (initialCategory ? REPORTS.filter((report) => report.category === initialCategory) : REPORTS),
|
||||
[initialCategory],
|
||||
);
|
||||
const [selectedKey, setSelectedKey] = useState<ReportKey>(
|
||||
filteredReports[0]?.key ?? REPORTS[0].key,
|
||||
);
|
||||
const [filters, setFilters] = useState<ReportFilters>(emptyFilters);
|
||||
const [result, setResult] = useState<ReportResult | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -422,8 +429,16 @@ export function ReportsConsole() {
|
||||
const [search, setSearch] = useState<SearchState>(emptySearch);
|
||||
const playOptions = useCachedPlayTypeOptions();
|
||||
const tRef = useTranslationRef(["reports", "common"]);
|
||||
const searchParams = useSearchParams();
|
||||
const drawNoFromUrl = (searchParams.get("draw_no") ?? "").trim();
|
||||
|
||||
const selectedReport = REPORTS.find((report) => report.key === selectedKey) ?? REPORTS[0];
|
||||
const selectedReport = filteredReports.find((report) => report.key === selectedKey) ?? filteredReports[0] ?? REPORTS[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (!filteredReports.some((report) => report.key === selectedKey)) {
|
||||
setSelectedKey(filteredReports[0]?.key ?? REPORTS[0].key);
|
||||
}
|
||||
}, [filteredReports, selectedKey]);
|
||||
|
||||
const pageScopedLabel = useCallback(
|
||||
(statKey: string) => `${t(`preview.stats.${statKey}`)} · ${t("preview.scope.currentPage")}`,
|
||||
@@ -621,7 +636,9 @@ export function ReportsConsole() {
|
||||
break;
|
||||
}
|
||||
case "daily_profit": {
|
||||
const payload = await getAdminReportDailyProfit(reportListParams(filters, page, perPage));
|
||||
const payload = await getAdminReportDailyProfit(
|
||||
reportListParams(filters, page, perPage),
|
||||
);
|
||||
const rows = payload.items.map((item) => ({
|
||||
business_date: item.business_date,
|
||||
total_bet_minor: item.total_bet_minor,
|
||||
@@ -650,7 +667,9 @@ export function ReportsConsole() {
|
||||
break;
|
||||
}
|
||||
case "player_win_loss": {
|
||||
const payload = await getAdminReportPlayerWinLoss(reportListParams(filters, page, perPage));
|
||||
const payload = await getAdminReportPlayerWinLoss(
|
||||
reportListParams(filters, page, perPage),
|
||||
);
|
||||
const rows = payload.items.map((item) => ({
|
||||
player_id: item.player_id,
|
||||
username: item.username,
|
||||
@@ -806,7 +825,9 @@ export function ReportsConsole() {
|
||||
break;
|
||||
}
|
||||
case "play_dimension": {
|
||||
const payload = await getAdminReportPlayDimension(reportListParams(filters, page, perPage));
|
||||
const payload = await getAdminReportPlayDimension(
|
||||
reportListParams(filters, page, perPage),
|
||||
);
|
||||
const rows = payload.items.map((item) => ({
|
||||
play_code: item.play_code,
|
||||
dimension: item.dimension,
|
||||
@@ -829,7 +850,9 @@ export function ReportsConsole() {
|
||||
break;
|
||||
}
|
||||
case "rebate_commission": {
|
||||
const payload = await getAdminReportRebateCommission(reportListParams(filters, page, perPage));
|
||||
const payload = await getAdminReportRebateCommission(
|
||||
reportListParams(filters, page, perPage),
|
||||
);
|
||||
const rows = payload.items.map((item) => ({
|
||||
play_code: item.play_code,
|
||||
total_rebate_minor: item.total_rebate_minor,
|
||||
@@ -906,13 +929,30 @@ export function ReportsConsole() {
|
||||
});
|
||||
}, [selectedKey]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
drawNo: drawNoFromUrl || prev.drawNo,
|
||||
}));
|
||||
if (drawNoFromUrl) {
|
||||
setSelectedKey("draw_profit");
|
||||
}
|
||||
}, [drawNoFromUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
queueMicrotask(() => {
|
||||
setResult(null);
|
||||
setError(null);
|
||||
setPage(1);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (result && result.key === selectedReport.key && selectedReport.connected) {
|
||||
queueMicrotask(() => {
|
||||
void queryReport();
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page, perPage]);
|
||||
|
||||
function updateFilter<K extends keyof ReportFilters>(key: K, value: ReportFilters[K]): void {
|
||||
@@ -1394,7 +1434,7 @@ export function ReportsConsole() {
|
||||
<CardTitle className="admin-list-title">{t("chooseReport")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-1.5 pt-3">
|
||||
{REPORTS.map((report) => {
|
||||
{filteredReports.map((report) => {
|
||||
const Icon = report.icon;
|
||||
const active = report.key === selectedReport.key;
|
||||
return (
|
||||
@@ -1431,13 +1471,6 @@ export function ReportsConsole() {
|
||||
<CardContent className="space-y-4 pt-4">
|
||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{selectedReport.fields.map(renderField)}
|
||||
{selectedReport.category === "profit" || selectedReport.category === "wallet" ? (
|
||||
<AdminAgentFilter
|
||||
id="report-agent-filter"
|
||||
value={filters.agentNodeId}
|
||||
onChange={(id) => setFilters((prev) => ({ ...prev, agentNodeId: id }))}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 border-t border-border/60 pt-4 sm:flex-row sm:items-center sm:justify-end">
|
||||
<div className="flex shrink-0 gap-2">
|
||||
|
||||
Reference in New Issue
Block a user