Some checks failed
lotteryadmin CI / build (push) Has been cancelled
- Simplified share rate calculation in AgentsConsole by removing unnecessary checks and directly setting the profile share rate. - Updated the use of `profileParentCaps` to always return total share rate in the agent profile. - Removed unused variables and memoized calculations for improved performance. - Cleaned up imports in various files, removing unused components and optimizing the code structure. - Added `tRef` dependency to several useEffect hooks to ensure proper reactivity to translation changes. - Enhanced report preview tables with better label handling for various statuses and actions. - Updated wallet filter options to align with player-side transaction types. - Introduced new properties in types for better type safety and clarity.
160 lines
6.2 KiB
TypeScript
160 lines
6.2 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useAsyncEffect } from "@/hooks/use-async-effect";
|
|
import { useTranslationRef } from "@/hooks/use-translation-ref";
|
|
import { toast } from "sonner";
|
|
import { Download, RefreshCw } from "lucide-react";
|
|
|
|
import { downloadAdminReportJob, getAdminReportJobs } from "@/api/admin-report-jobs";
|
|
import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu";
|
|
import { AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state";
|
|
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { AdminTableLoadingRow } from "@/components/admin/admin-loading-state";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
|
import { LotteryApiBizError } from "@/types/api/errors";
|
|
import type { AdminReportJobRow } from "@/types/api/admin-report-jobs";
|
|
|
|
function downloadBlob(blob: Blob, filename: string): void {
|
|
const url = URL.createObjectURL(blob);
|
|
const anchor = document.createElement("a");
|
|
anchor.href = url;
|
|
anchor.download = filename;
|
|
document.body.appendChild(anchor);
|
|
anchor.click();
|
|
anchor.remove();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
type ReportJobsPanelProps = {
|
|
canExport: boolean;
|
|
refreshToken?: number;
|
|
reportType?: string;
|
|
};
|
|
|
|
export function ReportJobsPanel({ canExport, refreshToken = 0, reportType }: ReportJobsPanelProps) {
|
|
const { t } = useTranslation(["reports", "common"]);
|
|
const tRef = useTranslationRef(["reports", "common"]);
|
|
const formatTs = useAdminDateTimeFormatter();
|
|
const [jobs, setJobs] = useState<AdminReportJobRow[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [downloadingId, setDownloadingId] = useState<number | null>(null);
|
|
|
|
const loadJobs = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await getAdminReportJobs({ page: 1, per_page: 10, report_type: reportType || undefined });
|
|
setJobs(data.items);
|
|
} catch (e) {
|
|
toast.error(e instanceof LotteryApiBizError ? e.message : tRef.current("tasks.loadFailed"));
|
|
setJobs([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [reportType, tRef]);
|
|
|
|
useAsyncEffect(() => {
|
|
void loadJobs();
|
|
}, [refreshToken]);
|
|
|
|
async function handleDownload(job: AdminReportJobRow): Promise<void> {
|
|
if (!canExport || job.status !== "completed") {
|
|
return;
|
|
}
|
|
setDownloadingId(job.id);
|
|
try {
|
|
const { blob, filename } = await downloadAdminReportJob(job.id);
|
|
const fallback = `${job.job_no}.${job.export_format}`;
|
|
downloadBlob(blob, filename ?? fallback);
|
|
toast.success(t("tasks.downloadSuccess", { jobNo: job.job_no }));
|
|
} catch (e) {
|
|
toast.error(e instanceof LotteryApiBizError ? e.message : t("tasks.downloadFailed"));
|
|
} finally {
|
|
setDownloadingId(null);
|
|
}
|
|
}
|
|
|
|
function reportTypeLabel(reportType: string): string {
|
|
const key = `jobTypes.${reportType}`;
|
|
const label = t(key);
|
|
return label === key ? reportType : label;
|
|
}
|
|
|
|
return (
|
|
<Card className="admin-list-card">
|
|
<CardHeader className="admin-list-header flex flex-row items-center justify-between gap-3 pb-4">
|
|
<CardTitle className="admin-list-title">{t("recentTasks")}</CardTitle>
|
|
<Button type="button" variant="outline" size="sm" disabled={loading} onClick={() => void loadJobs()}>
|
|
<RefreshCw data-icon="inline-start" className={loading ? "animate-spin" : undefined} />
|
|
{t("tasks.refresh")}
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent className="pt-2">
|
|
<p className="mb-3 text-xs text-muted-foreground">
|
|
{reportType ? t("tasks.currentReportHint") : t("exportHint")}
|
|
</p>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>{t("tasks.columns.jobNo")}</TableHead>
|
|
<TableHead>{t("tasks.columns.report")}</TableHead>
|
|
<TableHead>{t("tasks.columns.format")}</TableHead>
|
|
<TableHead>{t("tasks.columns.status")}</TableHead>
|
|
<TableHead>{t("tasks.columns.createdAt")}</TableHead>
|
|
<TableHead className="sticky right-0 z-20 bg-muted w-14 text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">{t("tasks.columns.actions")}</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{loading ? (
|
|
<AdminTableLoadingRow colSpan={6} />
|
|
) : jobs.length === 0 ? (
|
|
<AdminTableNoResourceRow colSpan={6} />
|
|
) : (
|
|
jobs.map((job) => (
|
|
<TableRow key={job.id}>
|
|
<TableCell className="font-mono text-xs">{job.job_no}</TableCell>
|
|
<TableCell className="text-sm">{reportTypeLabel(job.report_type)}</TableCell>
|
|
<TableCell className="uppercase">{job.export_format}</TableCell>
|
|
<TableCell>
|
|
<AdminStatusBadge status={job.status}>
|
|
{t(`tasks.status.${job.status}`, { defaultValue: job.status })}
|
|
</AdminStatusBadge>
|
|
</TableCell>
|
|
<TableCell className="text-xs text-muted-foreground">
|
|
{formatTs(job.created_at ?? job.finished_at)}
|
|
</TableCell>
|
|
<TableCell className="sticky right-0 z-10 bg-card text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">
|
|
<AdminRowActionsMenu
|
|
busy={downloadingId === job.id}
|
|
actions={[
|
|
{
|
|
key: "download",
|
|
label: t("tasks.download"),
|
|
icon: Download,
|
|
disabled: !canExport || job.status !== "completed",
|
|
onClick: () => void handleDownload(job),
|
|
},
|
|
]}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|