ERP-node/frontend/components/admin/UserHistoryModal.tsx

256 lines
9.9 KiB
TypeScript

"use client";
import { useState, useEffect, useCallback } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { AlertCircle, User } from "lucide-react";
import { UserHistory, UserHistoryResponse } from "@/types/userHistory";
import { userAPI } from "@/lib/api/user";
import { Pagination, PaginationInfo } from "@/components/common/Pagination";
import { USER_HISTORY_TABLE_COLUMNS, STATUS_BADGE_VARIANTS, CHANGE_TYPE_BADGE_VARIANTS } from "@/constants/userHistory";
interface UserHistoryModalProps {
isOpen: boolean;
onClose: () => void;
userId: string;
userName: string;
}
/**
* 사용자 변경이력 모달 컴포넌트 (원본 JSP 로직 기반)
*/
export function UserHistoryModal({ isOpen, onClose, userId, userName }: UserHistoryModalProps) {
const [historyList, setHistoryList] = useState<UserHistory[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 페이지네이션 상태 (원본 JSP와 동일)
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(10); // 원본에서 하드코딩된 값
const [totalItems, setTotalItems] = useState(0);
const [maxPageSize, setMaxPageSize] = useState(1);
// 페이지네이션 정보 계산
const totalPages = Math.ceil(totalItems / pageSize);
const paginationInfo: PaginationInfo = {
currentPage,
totalPages: totalPages || 1,
totalItems,
itemsPerPage: pageSize,
startItem: totalItems > 0 ? (currentPage - 1) * pageSize + 1 : 0,
endItem: Math.min(currentPage * pageSize, totalItems),
};
// 변경이력 데이터 로드 (원본 JSP 로직 기반)
const loadUserHistory = useCallback(
async (pageToLoad: number) => {
if (!userId) return;
setIsLoading(true);
setError(null);
try {
const params = {
page: pageToLoad,
countPerPage: pageSize,
};
console.log("📊 사용자 변경이력 로드:", { userId, params });
const response: UserHistoryResponse = await userAPI.getHistory(userId, params);
console.log("📊 백엔드 응답:", response);
if (response && response.success && Array.isArray(response.data)) {
const responseTotal = response.total || 0;
// No 컬럼을 rowNum 값으로 설정 (페이징 고려)
const mappedHistoryList = response.data.map((item, index) => ({
...item,
no: item.rowNum || responseTotal - (pageToLoad - 1) * pageSize - index, // rowNum 우선, 없으면 계산
}));
setHistoryList(mappedHistoryList);
setTotalItems(responseTotal);
setMaxPageSize(response.maxPageSize || 1);
} else if (response && response.success && (!response.data || response.data.length === 0)) {
// 데이터가 비어있는 경우
console.log("📋 변경이력이 없습니다.");
setHistoryList([]);
setTotalItems(0);
setMaxPageSize(1);
} else {
setHistoryList([]);
setTotalItems(0);
setMaxPageSize(1);
setError(response?.message || "변경이력 조회에 실패했습니다.");
}
} catch (err) {
console.error("사용자 변경이력 로드 오류:", err);
setError(err instanceof Error ? err.message : "변경이력 조회 중 오류가 발생했습니다.");
setHistoryList([]);
setTotalItems(0);
setMaxPageSize(1);
} finally {
setIsLoading(false);
}
},
[userId, pageSize],
);
// 모달이 열릴 때마다 데이터 로드
useEffect(() => {
if (isOpen && userId) {
setCurrentPage(1); // 페이지 초기화
loadUserHistory(1); // 첫 페이지 로드
}
}, [isOpen, userId, loadUserHistory]);
// 페이지 변경 핸들러 (원본 JSP의 fnc_goPage와 유사)
const handlePageChange = (page: number) => {
setCurrentPage(page);
loadUserHistory(page);
};
// 상태에 따른 배지 색상 (원본 JSP 로직 기반)
const getStatusBadgeVariant = (status: string) => {
if (status === "active") return "default";
if (status === "inactive") return "secondary";
return STATUS_BADGE_VARIANTS[status as keyof typeof STATUS_BADGE_VARIANTS] || "outline";
};
// 변경 유형에 따른 배지 색상
const getChangeTypeBadgeVariant = (changeType: string) => {
return CHANGE_TYPE_BADGE_VARIANTS[changeType as keyof typeof CHANGE_TYPE_BADGE_VARIANTS] || "outline";
};
// 상태 텍스트 변환 (원본 JSP 로직: ${info.STATUS eq 'active' ? '활성화':'비활성화'})
const getStatusText = (status: string) => {
return status === "active" ? "활성화" : "비활성화";
};
// 날짜 포맷팅
const formatDate = (dateString: string) => {
if (!dateString) return "-";
try {
return new Date(dateString).toLocaleString("ko-KR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
} catch {
return dateString;
}
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="flex max-h-[90vh] max-w-6xl flex-col">
<DialogHeader className="flex-shrink-0">
<DialogTitle className="flex items-center gap-2">
<User className="h-5 w-5" />
</DialogTitle>
<div className="text-muted-foreground text-sm">
{userName} ({userId}) .
</div>
</DialogHeader>
<div className="flex min-h-0 flex-1 flex-col">
{/* 로딩 상태 */}
{isLoading && (
<div className="flex flex-1 items-center justify-center">
<div className="text-muted-foreground"> ...</div>
</div>
)}
{/* 에러 상태 */}
{error && !isLoading && (
<div className="flex flex-1 items-center justify-center">
<div className="text-destructive flex items-center gap-2">
<AlertCircle className="h-5 w-5" />
<span>{error}</span>
</div>
</div>
)}
{/* 변경이력 테이블 (원본 JSP 구조 기반) */}
{!isLoading && !error && (
<>
<div className="flex-1 overflow-auto rounded-md border">
<Table>
<TableHeader className="bg-background sticky top-0">
<TableRow>
{USER_HISTORY_TABLE_COLUMNS.map((column) => (
<TableHead key={column.key} className="text-center" style={{ width: column.width }}>
{column.label}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{historyList.length === 0 ? (
<TableRow>
<TableCell
colSpan={USER_HISTORY_TABLE_COLUMNS.length}
className="text-muted-foreground py-8 text-center"
>
.
</TableCell>
</TableRow>
) : (
historyList.map((history, index) => (
<TableRow key={index}>
<TableCell className="text-center font-mono text-sm">{history.no}</TableCell>
<TableCell className="text-center text-sm">{history.sabun || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.userId || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.userName || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.deptName || "-"}</TableCell>
<TableCell className="text-center">
<Badge variant={getStatusBadgeVariant(history.status || "")}>
{getStatusText(history.status || "")}
</Badge>
</TableCell>
<TableCell className="text-center">
<Badge variant={getChangeTypeBadgeVariant(history.historyType || "")}>
{history.historyType || "-"}
</Badge>
</TableCell>
<TableCell className="text-center text-sm">{history.writerName || "-"}</TableCell>
<TableCell className="text-center text-sm">
{history.regDateTitle || formatDate(history.regDate || "")}
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{/* 페이지네이션 (항상 표시) */}
<div className="flex-shrink-0 pt-4">
<Pagination
paginationInfo={paginationInfo}
onPageChange={handlePageChange}
onPageSizeChange={() => {}} // 페이지 크기는 고정 (원본과 동일)
/>
<div className="text-muted-foreground mt-2 text-center text-sm"> {totalItems}</div>
</div>
</>
)}
</div>
{/* 하단 버튼 */}
<div className="flex flex-shrink-0 justify-end border-t pt-4">
<Button variant="outline" onClick={onClose}>
</Button>
</div>
</DialogContent>
</Dialog>
);
}