feat: 관리자 추가 기능 작업
This commit is contained in:
@@ -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 };
|
||||
@@ -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>
|
||||
|
||||
+2
-2
@@ -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}
|
||||
|
||||
+4
-7
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user