feat: 관리자 추가 기능 작업

This commit is contained in:
2025-06-19 12:02:09 +09:00
parent ab433fd14b
commit 46a96a04fb
7 changed files with 271 additions and 15 deletions
@@ -0,0 +1,253 @@
"use client";
import { useState } from "react";
import {
keepPreviousData,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import {
getCoreRowModel,
type PaginationState,
type RowSelectionState,
type ColumnDef,
} from "@tanstack/react-table";
import {
Button,
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
Input,
Checkbox,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Label,
} from "@common/components";
import {
UserApi,
type UserListResponse,
type UserSearchParams,
} from "@/entities/user";
import { GroupApi } from "@/entities/group";
import { GroupUsersApi } from "@/entities/group-user";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable } from "@/shared/lib";
function AddAdminDialog() {
const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage);
const [open, setOpen] = useState(false);
const [userSearchQuery, setUserSearchQuery] = useState<UserSearchParams>({
keywordType: "",
keyword: "",
});
const [selectedGroupId, setSelectedGroupId] = useState("");
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const { data: groupOptions } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS],
queryFn: () =>
GroupApi.findPageGroup({
delYn: "N",
mngrYn: "Y",
page: 0,
size: 99,
}),
select: (data) =>
data.content.map((item) => ({
groupId: item.groupId,
groupNm: item.groupNm,
})),
enabled: open,
});
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, pagination, "normal-user"],
queryFn: () =>
UserApi.findPageUser({
...userSearchQuery,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
enabled: open,
placeholderData: keepPreviousData,
});
const columns: ColumnDef<UserListResponse>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="전체 선택"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="행 선택"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "userId",
header: "사용자ID",
},
{
accessorKey: "userName",
header: "이름",
},
{
accessorKey: "ogdpSeNm",
header: "소속",
},
{
accessorKey: "mphn",
header: "연락처",
},
];
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns: columns,
state: { pagination, rowSelection },
manualPagination: true,
enableRowSelection: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
getRowId: (row) => row.userUnqId,
},
dataTableOptions: {
viewPagination: true,
},
});
const handleAddAdmins = async () => {
const selectedRows = Object.keys(rowSelection);
if (selectedRows.length === 0 || !selectedGroupId) {
alert("그룹과 사용자를 선택해주세요.");
return;
}
try {
const usersToAdd = selectedRows.map((userUnqId) => ({
mode: "C",
groupId: selectedGroupId,
userUnqId,
}));
await GroupUsersApi.saveGroupUser(usersToAdd);
queryClient.invalidateQueries({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
});
alert("관리자 추가를 완료했습니다.", () => {
setOpen(false);
setRowSelection({});
setSelectedGroupId("");
});
} catch (error) {
console.error(error);
}
};
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
e.stopPropagation();
setPagination({ pageIndex: 0, pageSize: 10 });
query.refetch();
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>관리자 추가</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle>관리자 추가</DialogTitle>
</DialogHeader>
<form onSubmit={handleSearch} className="mb-4 flex items-end gap-2">
<Select
onValueChange={(value) =>
setUserSearchQuery({ ...userSearchQuery, keywordType: value })
}
>
<SelectTrigger className="h-[3.2rem] w-[12rem] !text-[1.3rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="userId">아이디</SelectItem>
<SelectItem value="userName">이름</SelectItem>
</SelectContent>
</Select>
<Input
value={userSearchQuery.keyword}
onChange={(e) =>
setUserSearchQuery({
...userSearchQuery,
keyword: e.target.value,
})
}
className="!text-[1.3rem]"
placeholder="검색어 입력"
/>
<Button type="submit">검색</Button>
</form>
{tableComponent}
<hr className="my-4" />
<div className="mb-4 flex items-center gap-2">
<Label className="mr-2 text-label">그룹</Label>
<Select onValueChange={(value) => setSelectedGroupId(value)}>
<SelectTrigger className="h-[3.2rem] w-[16rem] !text-[1.3rem]">
<SelectValue placeholder="그룹 선택" />
</SelectTrigger>
<SelectContent>
{groupOptions?.map((item) => (
<SelectItem key={item.groupId} value={item.groupId}>
{item.groupNm}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<DialogFooter>
<Button color="white" onClick={() => setOpen(false)}>
취소
</Button>
<Button
onClick={handleAddAdmins}
disabled={
Object.keys(rowSelection).length === 0 || !selectedGroupId
}
>
추가
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export { AddAdminDialog };
+8 -2
View File
@@ -13,11 +13,12 @@ import {
SelectTrigger,
SelectValue,
} from "@common/components";
import { GroupApi } from "@/entities/group";
import { UserApi, type UserSearchParams } from "@/entities/user";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/table-columns";
import { GroupApi } from "@/entities/group";
import { AddAdminDialog } from "./add-admin-dialog";
function AdminPage() {
const [adminQuery, setAdminQuery] = useState<UserSearchParams>({
@@ -134,7 +135,12 @@ function AdminPage() {
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">관리자 목록</h3>
<h3 className="text-2xl font-medium text-label">
관리자 목록(총 {query.data?.totalElements}명)
</h3>
<div className="flex gap-2">
<AddAdminDialog />
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
@@ -36,7 +36,7 @@ type AdditionalUserDialogProps = {
groupId?: string;
};
function AdditionalUserDialog({ groupId }: AdditionalUserDialogProps) {
function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage);
@@ -188,4 +188,4 @@ function AdditionalUserDialog({ groupId }: AdditionalUserDialogProps) {
);
}
export { AdditionalUserDialog };
export { AddUserDialog };
@@ -16,7 +16,7 @@ import { GroupUsersApi } from "@/entities/group-user";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable } from "@/shared/lib";
import { columns } from "../model/group-users-table-columns";
import { AdditionalUserDialog } from "./additional-user-dialog";
import { AddUserDialog } from "./add-user-dialog";
type GroupUsersSectionProps = {
groupId?: string;
@@ -95,7 +95,7 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
<div className="card-header">
<h3 className="text-2xl font-medium text-label">그룹 사용자 목록</h3>
<div className="flex gap-2">
<AdditionalUserDialog groupId={groupId} />
<AddUserDialog groupId={groupId} />
<Button
color="red"
disabled={table.getSelectedRowModel().rows.length === 0}
@@ -18,16 +18,13 @@ import {
} from "@/entities/user-organization";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/additional-user-table-columns";
import { columns } from "../model/add-user-table-columns";
type AdditionalUserDialogProps = {
type AddUserDialogProps = {
onAddUsers: (user: SearchUser[]) => void;
} & React.ComponentProps<typeof Dialog>;
function AdditionalUserDialog({
onAddUsers,
...props
}: AdditionalUserDialogProps) {
function AddUserDialog({ onAddUsers, ...props }: AddUserDialogProps) {
const [open, setOpen] = useState(false);
const [userNm, setUserNm] = useState("");
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
@@ -109,4 +106,4 @@ function AdditionalUserDialog({
);
}
export { AdditionalUserDialog };
export { AddUserDialog };
@@ -37,7 +37,7 @@ import { CodeApi, type ICode } from "@/entities/code";
import type { OrganizationTree } from "@/entities/organization";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { AdditionalUserDialog } from "./additional-user-dialog";
import { AddUserDialog } from "./add-user-dialog";
import { MoveOrganizationDialog } from "./move-organization-dialog";
type UserOrganizationSectionProps = {
@@ -220,7 +220,7 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
}
handleMoveOrganization={handleMoveOrganization}
/>
<AdditionalUserDialog onAddUsers={handleAddUsers} />
<AddUserDialog onAddUsers={handleAddUsers} />
<Button
size="sm"
color="red"