- Updated global CSS to center-align table headers and cells, ensuring a consistent layout. - Modified admin table components to replace switches with status badges for better clarity. - Enhanced internationalization support by adding new strings for version actions and validation messages in multiple locales. - Refactored configuration document screens to include version selection and improved user feedback on status changes.
222 lines
8.2 KiB
TypeScript
222 lines
8.2 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { useExportLabels } from "@/hooks/use-export-labels";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { getAdminRiskPoolLockLogs } from "@/api/admin-risk";
|
|
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
|
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
|
|
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 {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
|
import { useAdminPlayCodeLabel } from "@/hooks/use-admin-play-type-catalog";
|
|
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
|
import { riskActionTypeLabel, riskSourceReasonLabel } from "@/modules/risk/risk-display";
|
|
import { formatAdminMinorUnits } from "@/lib/money";
|
|
import { LotteryApiBizError } from "@/types/api/errors";
|
|
import type { AdminRiskLockLogListData, AdminRiskLockLogRow } from "@/types/api/admin-risk";
|
|
|
|
const ACTION_ALL = "__all__";
|
|
|
|
function riskActionFilterLabel(
|
|
value: string,
|
|
t: (key: string) => string,
|
|
): string {
|
|
if (value === ACTION_ALL) {
|
|
return t("noLimit");
|
|
}
|
|
return riskActionTypeLabel(value, t);
|
|
}
|
|
|
|
export function RiskLockLogsConsole({ drawId }: { drawId: number }) {
|
|
const { t } = useTranslation(["risk", "common"]);
|
|
const exportLabels = useExportLabels("riskLockLogs");
|
|
useAdminCurrencyCatalog();
|
|
const playCodeLabel = useAdminPlayCodeLabel();
|
|
const formatDt = useAdminDateTimeFormatter();
|
|
const [page, setPage] = useState(1);
|
|
const [perPage, setPerPage] = useState(10);
|
|
const [data, setData] = useState<AdminRiskLockLogListData | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const [draftNumber, setDraftNumber] = useState("");
|
|
const [appliedNumber, setAppliedNumber] = useState("");
|
|
const [draftAction, setDraftAction] = useState<string>(ACTION_ALL);
|
|
const [appliedAction, setAppliedAction] = useState<string>(ACTION_ALL);
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const d = await getAdminRiskPoolLockLogs(drawId, {
|
|
page,
|
|
per_page: perPage,
|
|
normalized_number: appliedNumber.trim() === "" ? undefined : appliedNumber.trim(),
|
|
action_type:
|
|
appliedAction === ACTION_ALL
|
|
? undefined
|
|
: (appliedAction as "lock" | "release"),
|
|
});
|
|
setData(d);
|
|
} catch (e) {
|
|
const msg =
|
|
e instanceof LotteryApiBizError ? e.message : t("loadLogsFailed");
|
|
setError(msg);
|
|
setData(null);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [drawId, page, perPage, appliedAction, appliedNumber, t]);
|
|
|
|
useEffect(() => {
|
|
queueMicrotask(() => {
|
|
void load();
|
|
});
|
|
}, [load]);
|
|
|
|
return (
|
|
<Card className="admin-list-card">
|
|
<CardHeader className="admin-list-header">
|
|
<CardTitle className="admin-list-title">{t("lockLogsTitle")}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="admin-list-content">
|
|
<div className="admin-list-toolbar">
|
|
<div className="admin-list-field">
|
|
<Label htmlFor="risk-log-number" className="sm:w-20 sm:shrink-0">
|
|
{t("number4d")}
|
|
</Label>
|
|
<Input
|
|
id="risk-log-number"
|
|
inputMode="numeric"
|
|
maxLength={4}
|
|
value={draftNumber}
|
|
className="w-full sm:w-32"
|
|
onChange={(e) => setDraftNumber(e.target.value.replace(/\D/g, "").slice(0, 4))}
|
|
placeholder={t("optional")}
|
|
/>
|
|
</div>
|
|
<div className="admin-list-field">
|
|
<Label htmlFor="risk-log-action" className="sm:w-20 sm:shrink-0">
|
|
{t("actionFilter")}
|
|
</Label>
|
|
<Select
|
|
modal={false}
|
|
value={draftAction}
|
|
onValueChange={(v) => {
|
|
if (v) setDraftAction(v);
|
|
}}
|
|
>
|
|
<SelectTrigger id="risk-log-action" size="sm" className="w-full sm:w-40">
|
|
<SelectValue>{riskActionFilterLabel(draftAction, t)}</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value={ACTION_ALL}>{t("noLimit")}</SelectItem>
|
|
<SelectItem value="lock">{t("lock")}</SelectItem>
|
|
<SelectItem value="release">{t("release")}</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="admin-list-actions">
|
|
<AdminTableExportButton
|
|
tableId={`risk-lock-logs-table-${drawId}`}
|
|
filename={exportLabels.filename}
|
|
sheetName={exportLabels.sheetName}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
size="sm"
|
|
onClick={() => {
|
|
setAppliedNumber(draftNumber);
|
|
setAppliedAction(draftAction);
|
|
setPage(1);
|
|
}}
|
|
>
|
|
{t("applyFilter")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
|
|
|
{loading && !data ? (
|
|
<p className="text-sm text-muted-foreground">{t("states.loading", { ns: "common" })}</p>
|
|
) : (
|
|
<>
|
|
<div className="admin-table-shell">
|
|
<Table id={`risk-lock-logs-table-${drawId}`}>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>{t("time")}</TableHead>
|
|
<TableHead>{t("searchNumber")}</TableHead>
|
|
<TableHead>{t("action")}</TableHead>
|
|
<TableHead className="text-center">{t("amount")}</TableHead>
|
|
<TableHead>{t("source")}</TableHead>
|
|
<TableHead>{t("ticketNo")}</TableHead>
|
|
<TableHead>{t("playCode")}</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{(data?.items ?? []).map((row: AdminRiskLockLogRow) => (
|
|
<TableRow key={row.id}>
|
|
<TableCell className="whitespace-nowrap text-xs text-muted-foreground">
|
|
{row.created_at ? formatDt(row.created_at) : "—"}
|
|
</TableCell>
|
|
<TableCell className="font-mono text-sm">{row.normalized_number}</TableCell>
|
|
<TableCell className="text-sm">
|
|
{riskActionTypeLabel(row.action_type, t)}
|
|
</TableCell>
|
|
<TableCell className="text-center text-sm tabular-nums">
|
|
{formatAdminMinorUnits(row.amount, data?.currency_code ?? "NPR")}
|
|
</TableCell>
|
|
<TableCell className="text-xs text-muted-foreground">
|
|
{riskSourceReasonLabel(row.source_reason, t)}
|
|
</TableCell>
|
|
<TableCell className="font-mono text-xs">{row.ticket_no ?? "—"}</TableCell>
|
|
<TableCell className="text-xs">{playCodeLabel(row.play_code)}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
|
|
{data ? (
|
|
<AdminListPaginationFooter
|
|
selectId={`risk-logs-${drawId}`}
|
|
total={data.meta.total}
|
|
page={data.meta.current_page}
|
|
lastPage={data.meta.last_page}
|
|
perPage={data.meta.per_page}
|
|
loading={loading}
|
|
onPerPageChange={(n) => {
|
|
setPerPage(n);
|
|
setPage(1);
|
|
}}
|
|
onPageChange={setPage}
|
|
/>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|