From abb428a3c9a756da4462d02f99f791cac90c1ef0 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 24 Apr 2025 13:59:26 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A1=B0=EC=A7=81=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/menu-authority-service.ts | 6 +- .../model/menu-authority-interface.ts | 6 +- .../model/menu-authority-table-columns.tsx | 4 +- .../ui/management-authority-dialog.tsx | 4 +- .../entities/posting/api/posting-service.ts | 4 +- apps/cs/src/entities/posting/index.ts | 2 +- .../posting/model/posting-interface.ts | 4 +- .../entities/posting/ui/posting-dialog.tsx | 4 +- .../api/user-organization-service.ts | 15 +++- .../src/entities/user-organization/index.ts | 3 +- .../model/user-organization-interface.ts | 11 ++- apps/cs/src/pages/banner/ui/modify-page.tsx | 4 +- apps/cs/src/pages/image/ui/modify-page.tsx | 4 +- .../ui/additional-user-dialog.tsx | 2 +- .../ui/move-organization-dialog.tsx | 84 +++++++++++++++++++ .../ui/user-organization-section.tsx | 37 +++++++- .../src/widgets/dnd-tree/ui/custom-node.tsx | 5 +- apps/cs/src/widgets/dnd-tree/ui/dnd-tree.tsx | 28 ++++--- 18 files changed, 184 insertions(+), 43 deletions(-) create mode 100644 apps/cs/src/pages/organization/ui/move-organization-dialog.tsx diff --git a/apps/cs/src/entities/menu-authority/api/menu-authority-service.ts b/apps/cs/src/entities/menu-authority/api/menu-authority-service.ts index f9b01b4d8..22e3dcefa 100644 --- a/apps/cs/src/entities/menu-authority/api/menu-authority-service.ts +++ b/apps/cs/src/entities/menu-authority/api/menu-authority-service.ts @@ -3,7 +3,7 @@ import { ENDPOINT } from "@/shared/config"; import type { MenuAuthorityList, MenuAuthorityParams, - MenuAuthorityPayloadData, + MenuAuthorityPayload, MenuAuthorityService, } from "../model/menu-authority-interface"; @@ -18,9 +18,7 @@ export class MenuAuthorityApiService implements MenuAuthorityService { return response.data; } - public async saveMenuAuthority( - data: MenuAuthorityPayloadData[], - ): Promise { + public async saveMenuAuthority(data: MenuAuthorityPayload[]): Promise { const response = await client.post( `${ENDPOINT.CMS_SERVICE.MENU_AUTHRTS}`, data, diff --git a/apps/cs/src/entities/menu-authority/model/menu-authority-interface.ts b/apps/cs/src/entities/menu-authority/model/menu-authority-interface.ts index d6775838c..7467728fe 100644 --- a/apps/cs/src/entities/menu-authority/model/menu-authority-interface.ts +++ b/apps/cs/src/entities/menu-authority/model/menu-authority-interface.ts @@ -30,12 +30,10 @@ export const menuAuthorityFormSchema = z.object({ export type MenuAuthority = z.infer; export type MenuAuthorityList = PageableList; -export type MenuAuthorityPayloadData = z.infer< - typeof menuAuthorityPayloadSchema ->; +export type MenuAuthorityPayload = z.infer; export type MenuAuthorityFormData = z.infer; export interface MenuAuthorityService { getMenuAuthorityList(params: MenuAuthorityParams): Promise; - saveMenuAuthority(data: MenuAuthorityPayloadData[]): Promise; + saveMenuAuthority(data: MenuAuthorityPayload[]): Promise; } diff --git a/apps/cs/src/entities/menu-authority/model/menu-authority-table-columns.tsx b/apps/cs/src/entities/menu-authority/model/menu-authority-table-columns.tsx index 1a9863e15..b70731542 100644 --- a/apps/cs/src/entities/menu-authority/model/menu-authority-table-columns.tsx +++ b/apps/cs/src/entities/menu-authority/model/menu-authority-table-columns.tsx @@ -4,7 +4,7 @@ import { Checkbox } from "@common/components/ui"; import type { MenuAuthority, MenuAuthorityFormData, - MenuAuthorityPayloadData, + MenuAuthorityPayload, } from "./menu-authority-interface"; export const createColumns = ( @@ -13,7 +13,7 @@ export const createColumns = ( const getCurrentRow = (i: number) => form.watch("menuAuthority")[i]; const handleCheckedChange = - (i: number, key: keyof MenuAuthorityPayloadData) => (checked: boolean) => { + (i: number, key: keyof MenuAuthorityPayload) => (checked: boolean) => { if (getCurrentRow(i)?.mode === "R") { form.setValue(`menuAuthority.${i}.mode`, "U"); } diff --git a/apps/cs/src/entities/menu-authority/ui/management-authority-dialog.tsx b/apps/cs/src/entities/menu-authority/ui/management-authority-dialog.tsx index 62915e2ed..ea5bbb788 100644 --- a/apps/cs/src/entities/menu-authority/ui/management-authority-dialog.tsx +++ b/apps/cs/src/entities/menu-authority/ui/management-authority-dialog.tsx @@ -19,7 +19,7 @@ import { useAlertStore } from "@/shared/lib/use-alert-store"; import { menuAuthorityFormSchema, type MenuAuthorityFormData, - type MenuAuthorityPayloadData, + type MenuAuthorityPayload, } from "../model/menu-authority-interface"; import { createColumns } from "../model/menu-authority-table-columns"; import { MenuAuthorityApi } from "../api/menu-authority-service"; @@ -42,7 +42,7 @@ function ManagementAuthorityDialog({ }); const { mutateAsync: saveMenuAuthority } = useMutation({ - mutationFn: (data: MenuAuthorityPayloadData[]) => + mutationFn: (data: MenuAuthorityPayload[]) => MenuAuthorityApi.saveMenuAuthority(data), onSuccess: () => { alert("저장되었습니다."); diff --git a/apps/cs/src/entities/posting/api/posting-service.ts b/apps/cs/src/entities/posting/api/posting-service.ts index 2fafd6557..d1360b46a 100644 --- a/apps/cs/src/entities/posting/api/posting-service.ts +++ b/apps/cs/src/entities/posting/api/posting-service.ts @@ -5,7 +5,7 @@ import type { PostService, PostingParams, Post, - PostPayloadData, + PostPayload, } from "../model/posting-interface"; export class PostingApiService implements PostService { @@ -19,7 +19,7 @@ export class PostingApiService implements PostService { return response.data; } - public async savePosting(data: PostPayloadData[]): Promise { + public async savePosting(data: PostPayload[]): Promise { const response = await client.post( `${ENDPOINT.CMS_SERVICE.POSTINGS}`, data, diff --git a/apps/cs/src/entities/posting/index.ts b/apps/cs/src/entities/posting/index.ts index c6029d757..75c5c69ed 100644 --- a/apps/cs/src/entities/posting/index.ts +++ b/apps/cs/src/entities/posting/index.ts @@ -2,6 +2,6 @@ export { PostingApi } from "./api/posting-service"; export { POSTING_TYPE, type Post, - type PostPayloadData, + type PostPayload, } from "./model/posting-interface"; export { PostingDialog } from "./ui/posting-dialog"; diff --git a/apps/cs/src/entities/posting/model/posting-interface.ts b/apps/cs/src/entities/posting/model/posting-interface.ts index b14b2ab66..72c49bb03 100644 --- a/apps/cs/src/entities/posting/model/posting-interface.ts +++ b/apps/cs/src/entities/posting/model/posting-interface.ts @@ -41,10 +41,10 @@ export const postingFormSchema = z.object({ }); export type Post = z.infer; -export type PostPayloadData = z.infer; +export type PostPayload = z.infer; export type PostingFormData = z.infer; export interface PostService { getPostingHistory: (params: PostingParams) => Promise>; - savePosting: (data: PostPayloadData[]) => Promise; + savePosting: (data: PostPayload[]) => Promise; } diff --git a/apps/cs/src/entities/posting/ui/posting-dialog.tsx b/apps/cs/src/entities/posting/ui/posting-dialog.tsx index 9e27a4485..c461bcda9 100644 --- a/apps/cs/src/entities/posting/ui/posting-dialog.tsx +++ b/apps/cs/src/entities/posting/ui/posting-dialog.tsx @@ -34,7 +34,7 @@ import { postingFormSchema, type PostingFormData, type PostingType, - type PostPayloadData, + type PostPayload, } from "../model/posting-interface"; import { useAlertStore } from "@/shared/lib/use-alert-store"; @@ -76,7 +76,7 @@ function PostingDialog({ postingType, postingTargets }: PostingDialogProps) { }); const { mutateAsync: savePosting } = useMutation({ - mutationFn: (data: PostPayloadData[]) => PostingApi.savePosting(data), + mutationFn: (data: PostPayload[]) => PostingApi.savePosting(data), onSuccess: () => { alert("저장되었습니다."); }, diff --git a/apps/cs/src/entities/user-organization/api/user-organization-service.ts b/apps/cs/src/entities/user-organization/api/user-organization-service.ts index bbb7abeef..a4e588310 100644 --- a/apps/cs/src/entities/user-organization/api/user-organization-service.ts +++ b/apps/cs/src/entities/user-organization/api/user-organization-service.ts @@ -5,10 +5,11 @@ import type { UserOrganizationParams, UserOrganizationList, UserOrganizationService, - UserOrganizationPayloadData, + UserOrganizationPayload, SearchUserParams, SearchUserList, User, + MoveUserOrganizationPayload, } from "../model/user-organization-interface"; export class UserOrganizationApiService implements UserOrganizationService { @@ -38,7 +39,7 @@ export class UserOrganizationApiService implements UserOrganizationService { } public async saveUserOrganization( - data: UserOrganizationPayloadData[], + data: UserOrganizationPayload[], ): Promise { const response = await client.post( `${ENDPOINT.CMS_SERVICE.USER_ORGANIZATION}`, @@ -46,6 +47,16 @@ export class UserOrganizationApiService implements UserOrganizationService { ); return response.data; } + + public async moveUserOrganization( + data: MoveUserOrganizationPayload[], + ): Promise { + const response = await client.post( + `${ENDPOINT.CMS_SERVICE.USER_ORGANIZATION}/move-ognz`, + data, + ); + return response.data; + } } export const UserOrganizationApi = new UserOrganizationApiService(); diff --git a/apps/cs/src/entities/user-organization/index.ts b/apps/cs/src/entities/user-organization/index.ts index 19cd76682..87317f1da 100644 --- a/apps/cs/src/entities/user-organization/index.ts +++ b/apps/cs/src/entities/user-organization/index.ts @@ -4,7 +4,8 @@ export { type TreeUserOrganization, type TreeUser, type UserOrganizationFormData, - type UserOrganizationPayloadData, + type UserOrganizationPayload, type User, type SearchUser, + type MoveUserOrganizationPayload, } from "./model/user-organization-interface"; diff --git a/apps/cs/src/entities/user-organization/model/user-organization-interface.ts b/apps/cs/src/entities/user-organization/model/user-organization-interface.ts index d7901c86b..25d7c7032 100644 --- a/apps/cs/src/entities/user-organization/model/user-organization-interface.ts +++ b/apps/cs/src/entities/user-organization/model/user-organization-interface.ts @@ -53,6 +53,12 @@ export interface TreeUserOrganization { children: TreeUserOrganization[]; } +export interface MoveUserOrganizationPayload { + ognzSn: number; + toOgnzSn: number; + userUnqId: string; +} + export const userOrganizationPayloadSchema = z.object({ mode: z.string(), ognzSn: z.number(), @@ -68,7 +74,7 @@ export const userOrganizationFormSchema = z.object({ export type UserOrganizationList = PageableList; export type SearchUserList = PageableList; -export type UserOrganizationPayloadData = z.infer< +export type UserOrganizationPayload = z.infer< typeof userOrganizationPayloadSchema >; export type UserOrganizationFormData = z.infer< @@ -81,5 +87,6 @@ export interface UserOrganizationService { ): Promise; getUserOrganizationTreeList(): Promise; searchUser(params: SearchUserParams): Promise; - saveUserOrganization(data: UserOrganizationPayloadData[]): Promise; + saveUserOrganization(data: UserOrganizationPayload[]): Promise; + moveUserOrganization(data: MoveUserOrganizationPayload[]): Promise; } diff --git a/apps/cs/src/pages/banner/ui/modify-page.tsx b/apps/cs/src/pages/banner/ui/modify-page.tsx index 4063b83c4..f0f679529 100644 --- a/apps/cs/src/pages/banner/ui/modify-page.tsx +++ b/apps/cs/src/pages/banner/ui/modify-page.tsx @@ -12,7 +12,7 @@ import { } from "@tanstack/react-table"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { Button, DataTable } from "@common/components/ui"; -import { PostingApi, type PostPayloadData } from "@/entities/posting"; +import { PostingApi, type PostPayload } from "@/entities/posting"; import { useAlertStore } from "@/shared/lib/use-alert-store"; import { ENDPOINT } from "@/shared/config"; import { @@ -58,7 +58,7 @@ function ModifyPage({ sequential }: { sequential: string }) { }); const { mutateAsync: savePosting } = useMutation({ - mutationFn: (data: PostPayloadData[]) => PostingApi.savePosting(data), + mutationFn: (data: PostPayload[]) => PostingApi.savePosting(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, sequential], diff --git a/apps/cs/src/pages/image/ui/modify-page.tsx b/apps/cs/src/pages/image/ui/modify-page.tsx index 79e26c811..ec6030531 100644 --- a/apps/cs/src/pages/image/ui/modify-page.tsx +++ b/apps/cs/src/pages/image/ui/modify-page.tsx @@ -12,7 +12,7 @@ import { } from "@tanstack/react-table"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { Button, DataTable } from "@common/components/ui"; -import { PostingApi, type PostPayloadData } from "@/entities/posting"; +import { PostingApi, type PostPayload } from "@/entities/posting"; import { useAlertStore } from "@/shared/lib/use-alert-store"; import { ENDPOINT } from "@/shared/config"; import { imageFormSchema, type ImageFormData } from "../model/image-interface"; @@ -54,7 +54,7 @@ function ModifyPage({ sequential }: { sequential: string }) { }); const { mutateAsync: savePosting } = useMutation({ - mutationFn: (data: PostPayloadData[]) => PostingApi.savePosting(data), + mutationFn: (data: PostPayload[]) => PostingApi.savePosting(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, sequential], diff --git a/apps/cs/src/pages/organization/ui/additional-user-dialog.tsx b/apps/cs/src/pages/organization/ui/additional-user-dialog.tsx index 0dc63dcb0..9350b0897 100644 --- a/apps/cs/src/pages/organization/ui/additional-user-dialog.tsx +++ b/apps/cs/src/pages/organization/ui/additional-user-dialog.tsx @@ -71,7 +71,7 @@ function AdditionalUserDialog({ return ( - diff --git a/apps/cs/src/pages/organization/ui/move-organization-dialog.tsx b/apps/cs/src/pages/organization/ui/move-organization-dialog.tsx new file mode 100644 index 000000000..7951aa302 --- /dev/null +++ b/apps/cs/src/pages/organization/ui/move-organization-dialog.tsx @@ -0,0 +1,84 @@ +import { useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import type { NodeModel } from "@minoru/react-dnd-treeview"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@common/components/ui"; +import { DndTree } from "@/widgets/dnd-tree"; +import { + generateOrganizationTreeData, + type OrganizationTree, +} from "@/entities/organization"; +import { ENDPOINT } from "@/shared/config"; + +type MoveOrganizationDialogProps = { + buttonDisabled?: boolean; + handleMoveOrganization: (organization: OrganizationTree) => Promise; +}; + +function MoveOrganizationDialog({ + buttonDisabled, + handleMoveOrganization, +}: MoveOrganizationDialogProps) { + const { confirm } = useConfirm(); + const queryClient = useQueryClient(); + const queryData = queryClient.getQueryData([ + ENDPOINT.CMS_SERVICE.ORGANIZATIONS, + ]); + const organizationTreeData = generateOrganizationTreeData(queryData ?? []); + + const [open, setOpen] = useState(false); + const [selectedOrganization, setSelectedOrganization] = + useState(null); + + const handleNodeSelect = (node: NodeModel) => { + if (!node.data) return; + setSelectedOrganization(node.data); + }; + + const onMoveOrganization = async () => { + if (await confirm("이동하시겠습니까?")) { + await handleMoveOrganization(selectedOrganization as OrganizationTree); + setOpen(false); + } + }; + + return ( + + + + + + + 조직 이동 + + + + + + + + + + + ); +} + +export { MoveOrganizationDialog }; diff --git a/apps/cs/src/pages/organization/ui/user-organization-section.tsx b/apps/cs/src/pages/organization/ui/user-organization-section.tsx index d21d57be2..bbf763c73 100644 --- a/apps/cs/src/pages/organization/ui/user-organization-section.tsx +++ b/apps/cs/src/pages/organization/ui/user-organization-section.tsx @@ -30,14 +30,17 @@ import { UserOrganizationApi, userOrganizationFormSchema, type UserOrganizationFormData, - type UserOrganizationPayloadData, + type UserOrganizationPayload, type User, type SearchUser, + type MoveUserOrganizationPayload, } from "@/entities/user-organization"; import { CodeApi, type ICode } from "@/entities/code"; +import type { OrganizationTree } from "@/entities/organization"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore } from "@/shared/lib/use-alert-store"; import { AdditionalUserDialog } from "./additional-user-dialog"; +import { MoveOrganizationDialog } from "./move-organization-dialog"; type UserOrganizationSectionProps = { ognzSn: number; @@ -72,7 +75,7 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) { }); const { mutate: saveUserOrganization } = useMutation({ - mutationFn: (data: UserOrganizationPayloadData[]) => + mutationFn: (data: UserOrganizationPayload[]) => UserOrganizationApi.saveUserOrganization(data), onSuccess: () => { alert("저장되었습니다."); @@ -82,6 +85,17 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) { }, }); + const { mutate: moveUserOrganization } = useMutation({ + mutationFn: (data: MoveUserOrganizationPayload[]) => + UserOrganizationApi.moveUserOrganization(data), + onSuccess: () => { + alert("이동되었습니다."); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.CMS_SERVICE.USER_ORGANIZATION, ognzSn], + }); + }, + }); + const form = useForm({ resolver: zodResolver(userOrganizationFormSchema), values: { @@ -172,6 +186,17 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) { } }; + const handleMoveOrganization = async (organization: OrganizationTree) => { + const payloadData: MoveUserOrganizationPayload[] = selectedUsers.map( + (user) => ({ + ognzSn: user.ognzSn, + toOgnzSn: organization.ognzSn!, + userUnqId: user.userUnqId, + }), + ); + moveUserOrganization(payloadData); + }; + const isUserSelected = useCallback( (userUnqId: string) => selectedUsers.some((item) => item.userUnqId === userUnqId), @@ -181,6 +206,7 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) { useEffect(() => { if (userOrganizationList?.content) { setUserList(userOrganizationList.content); + setSelectedUsers([]); } }, [userOrganizationList]); @@ -189,6 +215,13 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {

소속원 목록

+