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

309 lines
8.2 KiB
TypeScript
Raw Normal View History

2025-10-27 16:40:59 +09:00
import { Key, History, Edit } from "lucide-react";
2025-08-21 09:41:46 +09:00
import { useState } from "react";
import { User } from "@/types/user";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { PaginationInfo } from "@/components/common/Pagination";
import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView";
2025-08-21 09:41:46 +09:00
import { UserStatusConfirmDialog } from "./UserStatusConfirmDialog";
import { UserHistoryModal } from "./UserHistoryModal";
interface UserTableProps {
users: User[];
isLoading: boolean;
paginationInfo: PaginationInfo;
onStatusToggle: (user: User, newStatus: string) => void;
onPasswordReset: (userId: string, userName: string) => void;
2025-10-27 16:40:59 +09:00
onEdit: (user: User) => void;
2025-08-21 09:41:46 +09:00
}
/**
*
*/
2025-10-27 16:40:59 +09:00
export function UserTable({
users,
isLoading,
paginationInfo,
onStatusToggle,
onPasswordReset,
onEdit,
}: UserTableProps) {
2025-08-21 09:41:46 +09:00
// 확인 모달 상태 관리
const [confirmDialog, setConfirmDialog] = useState<{
isOpen: boolean;
user: User | null;
newStatus: string;
}>({
isOpen: false,
user: null,
newStatus: "",
});
// 히스토리 모달 상태 관리
const [historyModal, setHistoryModal] = useState<{
isOpen: boolean;
userId: string;
userName: string;
}>({
isOpen: false,
userId: "",
userName: "",
});
// NO 컬럼 계산 함수 (페이지네이션 고려)
const getRowNumber = (index: number) => {
return paginationInfo.startItem + index;
};
// 날짜 포맷팅 함수
const formatDate = (dateString: string) => {
if (!dateString) return "-";
return dateString.split(" ")[0];
2025-08-21 09:41:46 +09:00
};
// 상태 토글 핸들러 (확인 모달 표시)
const handleStatusToggle = (user: User, checked: boolean) => {
const newStatus = checked ? "active" : "inactive";
setConfirmDialog({
isOpen: true,
user,
newStatus,
});
};
// 상태 변경 확인
const handleConfirmStatusChange = () => {
if (confirmDialog.user) {
onStatusToggle(confirmDialog.user, confirmDialog.newStatus);
}
setConfirmDialog({ isOpen: false, user: null, newStatus: "" });
};
// 상태 변경 취소
const handleCancelStatusChange = () => {
setConfirmDialog({ isOpen: false, user: null, newStatus: "" });
};
// 변경이력 모달 열기
const handleOpenHistoryModal = (user: User) => {
setHistoryModal({
isOpen: true,
2025-08-25 18:30:07 +09:00
userId: user.userId,
userName: user.userName || user.userId,
2025-08-21 09:41:46 +09:00
});
};
// 변경이력 모달 닫기
const handleCloseHistoryModal = () => {
setHistoryModal({
isOpen: false,
userId: "",
userName: "",
});
};
// 데스크톱 테이블 컬럼 정의
const columns: RDVColumn<User>[] = [
{
key: "no",
label: "No",
width: "60px",
render: (_value, _row, index) => (
<span className="font-mono font-medium">{getRowNumber(index)}</span>
),
},
{
key: "sabun",
label: "사번",
width: "80px",
hideOnMobile: true,
render: (value) => <span className="font-mono">{value || "-"}</span>,
},
{
key: "companyCode",
label: "회사",
width: "120px",
hideOnMobile: true,
render: (value) => <span className="font-medium">{value || "-"}</span>,
},
{
key: "deptName",
label: "부서명",
width: "120px",
hideOnMobile: true,
render: (value) => <span className="font-medium">{value || "-"}</span>,
},
{
key: "positionName",
label: "직책",
width: "100px",
hideOnMobile: true,
render: (value) => <span className="font-medium">{value || "-"}</span>,
},
{
key: "userId",
label: "사용자 ID",
width: "120px",
hideOnMobile: true,
render: (value) => <span className="font-mono">{value}</span>,
},
{
key: "userName",
label: "사용자명",
width: "100px",
hideOnMobile: true,
render: (value) => <span className="font-medium">{value}</span>,
},
{
key: "tel",
label: "전화번호",
width: "120px",
hideOnMobile: true,
render: (_value, row) => <span>{row.tel || row.cellPhone || "-"}</span>,
},
{
key: "email",
label: "이메일",
width: "200px",
hideOnMobile: true,
className: "max-w-[200px] truncate",
render: (value, row) => (
<span title={row.email}>{value || "-"}</span>
),
},
{
key: "regDate",
label: "등록일",
width: "100px",
hideOnMobile: true,
render: (value) => <span>{formatDate(value || "")}</span>,
},
{
key: "status",
label: "상태",
width: "120px",
hideOnMobile: true,
render: (_value, row) => (
<div className="flex items-center">
<Switch
checked={row.status === "active"}
onCheckedChange={(checked) => handleStatusToggle(row, checked)}
aria-label={`${row.userName} 상태 토글`}
/>
2025-10-22 14:52:13 +09:00
</div>
),
},
];
2025-10-22 14:52:13 +09:00
// 모바일 카드 필드 정의
const cardFields: RDVCardField<User>[] = [
{
label: "사번",
render: (user) => <span className="font-mono font-medium">{user.sabun || "-"}</span>,
hideEmpty: true,
},
{
label: "회사",
render: (user) => <span className="font-medium">{user.companyCode || ""}</span>,
hideEmpty: true,
},
{
label: "부서",
render: (user) => <span className="font-medium">{user.deptName || ""}</span>,
hideEmpty: true,
},
{
label: "직책",
render: (user) => <span className="font-medium">{user.positionName || ""}</span>,
hideEmpty: true,
},
{
label: "연락처",
render: (user) => <span>{user.tel || user.cellPhone || ""}</span>,
hideEmpty: true,
},
{
label: "이메일",
render: (user) => <span className="break-all">{user.email || ""}</span>,
hideEmpty: true,
},
{
label: "등록일",
render: (user) => <span>{formatDate(user.regDate || "")}</span>,
},
];
2025-10-22 14:52:13 +09:00
return (
<>
<ResponsiveDataView<User>
data={users}
columns={columns}
keyExtractor={(u) => u.userId}
isLoading={isLoading}
emptyMessage="등록된 사용자가 없습니다."
skeletonCount={10}
cardTitle={(u) => u.userName || ""}
cardSubtitle={(u) => <span className="font-mono">{u.userId}</span>}
cardHeaderRight={(u) => (
<Switch
checked={u.status === "active"}
onCheckedChange={(checked) => handleStatusToggle(u, checked)}
aria-label={`${u.userName} 상태 토글`}
/>
)}
cardFields={cardFields}
actionsLabel="작업"
actionsWidth="200px"
renderActions={(user) => (
<>
<Button
variant="ghost"
size="icon"
onClick={() => onEdit(user)}
className="h-8 w-8"
title="사용자 정보 수정"
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => onPasswordReset(user.userId, user.userName || user.userId)}
className="h-8 w-8"
title="비밀번호 초기화"
>
<Key className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleOpenHistoryModal(user)}
className="h-8 w-8"
title="변경이력 조회"
>
<History className="h-4 w-4" />
</Button>
</>
)}
/>
2025-08-21 09:41:46 +09:00
{/* 상태 변경 확인 모달 */}
<UserStatusConfirmDialog
user={confirmDialog.user}
newStatus={confirmDialog.newStatus}
isOpen={confirmDialog.isOpen}
onConfirm={handleConfirmStatusChange}
onCancel={handleCancelStatusChange}
/>
{/* 사용자 변경이력 모달 */}
<UserHistoryModal
isOpen={historyModal.isOpen}
onClose={handleCloseHistoryModal}
userId={historyModal.userId}
userName={historyModal.userName}
/>
2025-10-22 14:52:13 +09:00
</>
2025-08-21 09:41:46 +09:00
);
}