diff --git a/apps/cs/src/pages/menu/ui/editable-node.tsx b/apps/cs/src/pages/menu/ui/editable-node.tsx index f2274f4d8..4f1fe859c 100644 --- a/apps/cs/src/pages/menu/ui/editable-node.tsx +++ b/apps/cs/src/pages/menu/ui/editable-node.tsx @@ -1,5 +1,6 @@ import type { ComponentProps } from "react"; import { Button, Input } from "@common/components"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; type EditableNodeProps = { depth?: number; @@ -20,22 +21,34 @@ function EditableNode({ >
- - + + + + + 저장 + + + + + + 취소 +
); diff --git a/apps/cs/src/pages/role/api/site-authority-service.ts b/apps/cs/src/pages/role/api/site-authority-service.ts new file mode 100644 index 000000000..c9d0513da --- /dev/null +++ b/apps/cs/src/pages/role/api/site-authority-service.ts @@ -0,0 +1,28 @@ +import { client } from "@/shared/api"; +import type { + SiteAuthorityCreatePayload, + SiteAuthorityList, + SiteAuthoritySearchParams, + SiteAuthorityService, +} from "../model/site-authority-interface"; +import { ENDPOINT } from "@/shared/config"; + +export class SiteAuthorityApiService implements SiteAuthorityService { + public async getSiteAuthorityList( + params: SiteAuthoritySearchParams, + ): Promise { + const response = await client.get( + ENDPOINT.CMS_SERVICE.SITE_AUTHORITY, + { params }, + ); + return response.data; + } + + public async createSiteAuthority( + data: SiteAuthorityCreatePayload[], + ): Promise { + await client.post(ENDPOINT.CMS_SERVICE.SITE_AUTHORITY, data); + } +} + +export const SiteAuthorityApi = new SiteAuthorityApiService(); diff --git a/apps/cs/src/pages/role/model/site-authority-interface.ts b/apps/cs/src/pages/role/model/site-authority-interface.ts new file mode 100644 index 000000000..70ac79cfa --- /dev/null +++ b/apps/cs/src/pages/role/model/site-authority-interface.ts @@ -0,0 +1,24 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface SiteAuthoritySearchParams extends PageableParams { + siteId?: string; + authrtId?: string; +} + +export interface SiteAuthority { + siteId: string; + authrtId: string; + siteNm: string; + mode: string; +} + +export type SiteAuthorityCreatePayload = Omit; + +export type SiteAuthorityList = PageableList; + +export interface SiteAuthorityService { + getSiteAuthorityList: ( + params: SiteAuthoritySearchParams, + ) => Promise; + createSiteAuthority: (data: SiteAuthorityCreatePayload[]) => Promise; +} diff --git a/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx b/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx index c7f8cdcdb..46f3d45f0 100644 --- a/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx +++ b/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx @@ -1,5 +1,6 @@ -import { getCoreRowModel, type ColumnDef } from "@tanstack/react-table"; +import { useState, useRef } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { getCoreRowModel, type ColumnDef } from "@tanstack/react-table"; import { Dialog, DialogTrigger, @@ -8,6 +9,7 @@ import { DialogTitle, Button, Switch, + Input, } from "@common/components"; import { RoleAuthorizationsApi, @@ -31,12 +33,18 @@ function MappingRoleDialog({ }: MappingRoleDialogProps) { const queryClient = useQueryClient(); const alert = useAlertStore((state) => state.setMessage); + const inputRef = useRef(null); + const [searchParams, setSearchParams] = useState({ + keyword: "", + keywordType: "authorizationName", + }); const query = useQuery({ - queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS], + queryKey: [ENDPOINT.USER_SERVICE.ROLE_AUTHORIZATIONS, searchParams], queryFn: () => RoleAuthorizationsApi.getRoleAuthorizationsListByRoleId({ roleId, + ...searchParams, }), enabled: open, }); @@ -46,7 +54,7 @@ function MappingRoleDialog({ RoleAuthorizationsApi.createRoleAuthorizations(data), onSuccess: () => { queryClient.invalidateQueries({ - queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS], + queryKey: [ENDPOINT.USER_SERVICE.ROLE_AUTHORIZATIONS], }); alert("권한 맵핑이 완료되었습니다."); }, @@ -66,6 +74,26 @@ function MappingRoleDialog({ ); }; + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const formData = new FormData(e.target as HTMLFormElement); + const keyword = formData.get("keyword") as string; + setSearchParams({ + ...searchParams, + keyword, + }); + }; + + const handleReset = () => { + if (inputRef.current) { + inputRef.current.value = ""; + } + setSearchParams({ + ...searchParams, + keyword: "", + }); + }; + const { tableComponent } = useDataTable({ query, tableOptions: { @@ -79,21 +107,31 @@ function MappingRoleDialog({ - + 모듈 목록 -
-
- + - +
- {tableComponent}
+
{tableComponent}
); diff --git a/apps/cs/src/pages/role/ui/role-form.tsx b/apps/cs/src/pages/role/ui/role-form.tsx index 355872c9e..998ebbc2c 100644 --- a/apps/cs/src/pages/role/ui/role-form.tsx +++ b/apps/cs/src/pages/role/ui/role-form.tsx @@ -1,7 +1,9 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; import type { UseFormReturn } from "react-hook-form"; import { Button, + Checkbox, Input, Label, RadioGroup, @@ -16,6 +18,9 @@ import { FormWrapper } from "@/widgets/form-wrapper"; import { RoleApi, type RoleFormData } from "@/entities/role"; import { FormField, FormItem, FormControl } from "@/shared/ui/form"; import { useAlertStore, useMutationWrapper } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import { SiteAuthorityApi } from "../api/site-authority-service"; +import type { SiteAuthorityCreatePayload } from "../model/site-authority-interface"; type RoleFormProps = { roleId?: string; @@ -24,12 +29,24 @@ type RoleFormProps = { handleDelete?: () => Promise; }; -function RoleForm({ roleId, form, ...props }: RoleFormProps) { +function RoleForm({ roleId, form, handleSave, ...props }: RoleFormProps) { const isModifyMode = !!roleId; - + const queryClient = useQueryClient(); const alert = useAlertStore((state) => state.setMessage); - const [isValidRoleId, setIsValidRoleId] = useState(false); + const [checkedSiteAuthority, setCheckedSiteAuthority] = useState( + [], + ); + + const { data: siteAuthorityList } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.SITE_AUTHORITY, roleId], + queryFn: () => + SiteAuthorityApi.getSiteAuthorityList({ + authrtId: roleId, + page: 0, + size: 100, + }), + }); const { mutateAsync: checkDuplicateRoleId } = useMutationWrapper({ mutationFn: (roleId: string) => RoleApi.checkDuplicateRoleId(roleId), @@ -43,8 +60,49 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) { }, }); + const handleSiteAuthorityChecked = (checked: boolean, siteId: string) => { + if (checked) { + setCheckedSiteAuthority((prev) => [...prev, siteId]); + } else { + setCheckedSiteAuthority((prev) => prev.filter((id) => id !== siteId)); + } + }; + + const saveInterceptor = async (data: RoleFormData) => { + const siteAuthorityPayload: SiteAuthorityCreatePayload[] = + checkedSiteAuthority.map((siteId) => ({ + siteId, + authrtId: data.roleId, + mode: "C", + })); + + try { + await handleSave(data); + if (data.mngrYn === "N") return; + await SiteAuthorityApi.createSiteAuthority(siteAuthorityPayload); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.CMS_SERVICE.SITE_AUTHORITY], + }); + } catch (error) { + console.error(error); + } + }; + + useEffect(() => { + if (!siteAuthorityList?.content.length) return; + const checkedSiteAuthority = siteAuthorityList.content.filter( + (item) => item.mode === "R", + ); + setCheckedSiteAuthority(checkedSiteAuthority.map((item) => item.siteId)); + }, [siteAuthorityList]); + return ( - +
@@ -165,6 +223,29 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) { /> + {form.watch("mngrYn") === "Y" && ( + + 관리사이트 + +
+ {siteAuthorityList?.content.map((item) => ( +
+ { + handleSiteAuthorityChecked(checked, item.siteId); + }} + /> + {item.siteNm} +
+ ))} +
+
+
+ )} 사용여부 diff --git a/apps/cs/src/pages/site/ui/editable-node.tsx b/apps/cs/src/pages/site/ui/editable-node.tsx index 20ac90812..b297fa51b 100644 --- a/apps/cs/src/pages/site/ui/editable-node.tsx +++ b/apps/cs/src/pages/site/ui/editable-node.tsx @@ -1,5 +1,6 @@ import type { ComponentProps } from "react"; import { Button, Input } from "@common/components"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; type EditableNodeProps = { depth?: number; @@ -21,22 +22,34 @@ function EditableNode({ >
- - + + + + + 저장 + + + + + + 취소 +
); diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index cd639a628..120a45b2e 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -53,6 +53,7 @@ export const ENDPOINT = { GROUP_USERS: "/cms-service/api/v1/group-users", MANAGER_IP: "/cms-service/api/v1/mgrip", APILOG: "/cms-service/api/v1/api-logs", + SITE_AUTHORITY: "/cms-service/api/v1/site-authrt", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs", diff --git a/packages/common/components/src/ui/lnb/index.tsx b/packages/common/components/src/ui/lnb/index.tsx index 30698d17f..650fd2474 100644 --- a/packages/common/components/src/ui/lnb/index.tsx +++ b/packages/common/components/src/ui/lnb/index.tsx @@ -63,7 +63,7 @@ export function LNB({ function isPathMatched(path: string) { if (!path) return false; - return currentPath === path; + return currentPath?.startsWith(path); } useEffect(() => {