- Updated the user list retrieval logic to ensure proper filtering based on company codes, enhancing security for user data access. - Implemented checks to restrict access to company management APIs, allowing only SUPER_ADMIN users to perform actions related to company data. - Adjusted the user interface to reflect access restrictions for non-SUPER_ADMIN users, providing clear feedback when access is denied. These changes strengthen the integrity of user management and ensure that sensitive company information is only accessible to authorized personnel.
325 lines
8.7 KiB
TypeScript
325 lines
8.7 KiB
TypeScript
import { Key, History, Edit } from "lucide-react";
|
|
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";
|
|
import { UserStatusConfirmDialog } from "./UserStatusConfirmDialog";
|
|
import { UserHistoryModal } from "./UserHistoryModal";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
|
|
interface UserTableProps {
|
|
users: User[];
|
|
isLoading: boolean;
|
|
paginationInfo: PaginationInfo;
|
|
onStatusToggle: (user: User, newStatus: string) => void;
|
|
onPasswordReset: (userId: string, userName: string) => void;
|
|
onEdit: (user: User) => void;
|
|
}
|
|
|
|
/**
|
|
* 사용자 목록 테이블 컴포넌트
|
|
*/
|
|
export function UserTable({
|
|
users,
|
|
isLoading,
|
|
paginationInfo,
|
|
onStatusToggle,
|
|
onPasswordReset,
|
|
onEdit,
|
|
}: UserTableProps) {
|
|
const { user: currentUser } = useAuth();
|
|
const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";
|
|
|
|
// 확인 모달 상태 관리
|
|
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];
|
|
};
|
|
|
|
// 상태 토글 핸들러 (확인 모달 표시)
|
|
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,
|
|
userId: user.userId,
|
|
userName: user.userName || user.userId,
|
|
});
|
|
};
|
|
|
|
// 변경이력 모달 닫기
|
|
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>,
|
|
},
|
|
...(isSuperAdmin
|
|
? [
|
|
{
|
|
key: "companyCode" as keyof User,
|
|
label: "회사",
|
|
width: "120px",
|
|
hideOnMobile: true,
|
|
render: (value: any, user: User) => (
|
|
<span className="font-medium">{(user as any).companyName || 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} 상태 토글`}
|
|
/>
|
|
</div>
|
|
),
|
|
},
|
|
];
|
|
|
|
// 모바일 카드 필드 정의
|
|
const cardFields: RDVCardField<User>[] = [
|
|
{
|
|
label: "사번",
|
|
render: (user) => <span className="font-mono font-medium">{user.sabun || "-"}</span>,
|
|
hideEmpty: true,
|
|
},
|
|
...(isSuperAdmin
|
|
? [
|
|
{
|
|
label: "회사",
|
|
render: (user: User) => (
|
|
<span className="font-medium">{(user as any).companyName || 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>,
|
|
},
|
|
];
|
|
|
|
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>
|
|
</>
|
|
)}
|
|
/>
|
|
|
|
{/* 상태 변경 확인 모달 */}
|
|
<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}
|
|
/>
|
|
</>
|
|
);
|
|
}
|