Files
frontend/apps/st/src/components/statistics/management/LogHistoryDetailDialog.tsx
T

221 lines
7.4 KiB
TypeScript

"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { GridFitStyle } from "realgrid";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
LoadingWithAnimation,
} from "@common/components";
import DataGrid from "@/components/module/DataGrid";
import type {
ColumnInfo,
DataCellClickCallbackParams,
} from "@/components/type/DataGrid.type";
import {
alignLogDetailRowsToColumns,
buildLogHistoryDetailColumnInfo,
emptyLogHistoryDetailColumnInfo,
LOG_DETAIL_DETAILS_FIELD,
LOG_DETAIL_DETAILS_RAW_FIELD,
normalizeLogDetailList,
type LogDetailVerificationItem,
prepareLogDetailRowsForGrid,
} from "@/components/util/column/management/LogHistoryDetailColumn";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { UserApi } from "@/entities/user";
import { useAlertStore } from "@/shared/lib";
import { LogHistoryDetailsNestedDialog } from "./LogHistoryDetailsNestedDialog";
export interface LogHistoryDetailDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
row: Record<string, string | number> | null;
}
export function LogHistoryDetailDialog({
open,
onOpenChange,
row,
}: LogHistoryDetailDialogProps) {
const { setMessage: alert } = useAlertStore((state) => state);
const [isLoading, setIsLoading] = useState(false);
const [rowData, setRowData] = useState<Record<string, string | number>[]>(
[],
);
const [columnInfo, setColumnInfo] = useState<ColumnInfo>(
emptyLogHistoryDetailColumnInfo,
);
const [userName, setUserName] = useState("");
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
const [selectedDetails, setSelectedDetails] = useState<
LogDetailVerificationItem[]
>([]);
useEffect(() => {
if (!open) {
setRowData([]);
setColumnInfo(emptyLogHistoryDetailColumnInfo);
setUserName("");
setDetailsDialogOpen(false);
setSelectedDetails([]);
setIsLoading(false);
return;
}
if (!row) return;
const sn = row.statsLogSn;
const rgtrId =
row.rgtrId != null && String(row.rgtrId).trim() !== ""
? String(row.rgtrId).trim()
: "";
let cancelled = false;
void (async () => {
setIsLoading(true);
setRowData([]);
setUserName("");
try {
const userLookupPromise =
rgtrId !== ""
? UserApi.findByUserUnqId(rgtrId).catch(() => {
return null;
})
: Promise.resolve(null);
const [response, userResponse] = await Promise.all([
statisticsApi.getLogDetail(
sn !== undefined && sn !== null ? String(sn) : "",
),
userLookupPromise,
]);
if (userResponse) {
setUserName(userResponse.userName ?? "");
}
if (cancelled) return;
if (response.status !== "success") {
alert(
response.message ??
"로그 상세 정보를 불러오는데 실패했습니다.",
);
return;
}
const list = normalizeLogDetailList(response.data);
const nextColumnInfo = buildLogHistoryDetailColumnInfo(list);
const nextRows = alignLogDetailRowsToColumns(
prepareLogDetailRowsForGrid(list),
nextColumnInfo,
);
setColumnInfo(nextColumnInfo);
setRowData(nextRows);
} catch {
if (!cancelled) {
alert("로그 상세 정보를 불러오는데 실패했습니다.");
}
} finally {
if (!cancelled) setIsLoading(false);
}
})();
return () => {
cancelled = true;
};
}, [open, row, alert]);
const handleDataCellClick = useCallback(
({ fieldName, row }: DataCellClickCallbackParams) => {
if (fieldName !== LOG_DETAIL_DETAILS_FIELD) return;
const raw = row[LOG_DETAIL_DETAILS_RAW_FIELD];
if (raw == null || String(raw).trim() === "") return;
try {
const parsed = JSON.parse(String(raw)) as unknown;
if (!Array.isArray(parsed) || parsed.length === 0) return;
setSelectedDetails(parsed as LogDetailVerificationItem[]);
setDetailsDialogOpen(true);
} catch {
alert("상세 데이터를 불러오는데 실패했습니다.");
}
},
[alert],
);
const gridRemountKey = useMemo(
() => columnInfo.columns.map((c) => c.fieldName).join("\u0001"),
[columnInfo],
);
const showGrid = !isLoading && rowData.length > 0;
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
className="max-w-[80%]"
aria-describedby={undefined}
>
<DialogHeader>
<DialogTitle>로그 상세 정보(기능 실행자: {userName})</DialogTitle>
</DialogHeader>
<div className="relative min-h-[32rem]">
{isLoading && (
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
{showGrid ? (
<DataGrid
key={gridRemountKey}
columnInfo={columnInfo}
rowData={rowData}
setRowData={setRowData}
isExcelImport={false}
isExcelExport={false}
enableSort={false}
gridHeight={55}
fitStyle={GridFitStyle.EVEN}
onDataCellClick={handleDataCellClick}
/>
) : (
!isLoading && (
<p className="flex h-full items-center justify-center text-[1.3rem] text-muted-foreground">
표시할 상세 데이터가 없습니다.
</p>
)
)}
</div>
<DialogFooter>
<DialogClose asChild>
<Button size="lg" color="white" type="button">
닫기
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
<LogHistoryDetailsNestedDialog
open={detailsDialogOpen}
onOpenChange={setDetailsDialogOpen}
items={selectedDetails}
/>
</>
);
}