Merge branch 'iipbs_cs' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -74,7 +74,6 @@ function ManagementAuthorityDialog({ menuCd }: ManagementAuthorityDialogProps) {
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="gap-3" disabled={!menuCd}>
|
||||
<i className="diveicon di-personal-information" />
|
||||
권한관리
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { MenuItem, MenuService } from "../model/menu-interface";
|
||||
import type {
|
||||
MenuService,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuFormData,
|
||||
MenuTemporarilyStoredData,
|
||||
} from "../model/menu-interface";
|
||||
|
||||
export class MenuApiService implements MenuService {
|
||||
async getMenuRoles(siteId: string): Promise<MenuItem[]> {
|
||||
@@ -9,6 +15,56 @@ export class MenuApiService implements MenuService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMenuInfo(menuCd: number): Promise<Menu> {
|
||||
const response = await client.get<Menu>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createMenu(data: MenuFormData): Promise<void> {
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenu(
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deleteMenu(menuCd: number): Promise<void> {
|
||||
await client.delete(`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`);
|
||||
}
|
||||
|
||||
public async menuTemporaryStorage(
|
||||
menuCd: number,
|
||||
data: MenuFormData,
|
||||
): Promise<void> {
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENU_TEMPORARY_STORAGE}/${menuCd}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMenuTemporarilyStoredData(
|
||||
menuCd: number,
|
||||
): Promise<MenuTemporarilyStoredData[]> {
|
||||
const response = await client.get<MenuTemporarilyStoredData[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENU_TEMPORARY_STORAGE}/menu/${menuCd}/user`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const MenuApi = new MenuApiService();
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
export { MenuApi } from "./api/menu-service";
|
||||
export {
|
||||
MENU_TYPE,
|
||||
menuFormSchema,
|
||||
type MenuType,
|
||||
type MenuItem,
|
||||
type MenuFormData,
|
||||
} from "./model/menu-interface";
|
||||
export { MenuWrapper } from "./lib/menu-wrapper";
|
||||
export type { MenuItem } from "./model/menu-interface";
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MENU_TYPE = {
|
||||
EMPTY: "EMPTY",
|
||||
CONTENTS: "CONTENTS",
|
||||
BOARD: "BOARD",
|
||||
INSIDE: "INSIDE",
|
||||
OUTSIDE: "OUTSIDE",
|
||||
BOTTOM: "BOTTOM",
|
||||
} as const;
|
||||
|
||||
export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE];
|
||||
|
||||
export interface MenuItem {
|
||||
menuCd: number;
|
||||
menuKornNm: string;
|
||||
@@ -18,6 +31,65 @@ export interface MenuItem {
|
||||
requiredUrlPath: boolean;
|
||||
}
|
||||
|
||||
export interface MenuTemporarilyStoredData {
|
||||
tmprStrgSn: number;
|
||||
menuCd: number;
|
||||
menuTmprStrg: string;
|
||||
regDt: string;
|
||||
rgtr: string;
|
||||
mdfcnDt: string;
|
||||
mdfr: string;
|
||||
}
|
||||
|
||||
export const menuFormSchema = z.object({
|
||||
siteId: z.string(),
|
||||
menuKornNm: z.string(),
|
||||
menuSubKornNm: z.string().nullish(),
|
||||
menuEngNm: z.string().nullish(),
|
||||
menuSubEngNm: z.string().nullish(),
|
||||
levelNo: z.number(),
|
||||
urlPath: z.string().nullish(),
|
||||
iconNm: z.string().nullish(),
|
||||
menuLnkgTypeCd: z.enum([
|
||||
MENU_TYPE.EMPTY,
|
||||
MENU_TYPE.CONTENTS,
|
||||
MENU_TYPE.BOARD,
|
||||
MENU_TYPE.INSIDE,
|
||||
MENU_TYPE.OUTSIDE,
|
||||
MENU_TYPE.BOTTOM,
|
||||
]),
|
||||
menuLnkgSn: z.number().nullish(),
|
||||
otptYn: z.string().default("Y"),
|
||||
prntMenuCd: z.number().nullish(),
|
||||
sortSeq: z.number(),
|
||||
evlUseYn: z.string().default("Y"),
|
||||
qrcd: z.string().nullish(),
|
||||
useYn: z.string().default("Y"),
|
||||
newWndwYn: z.string().nullish(),
|
||||
menuLnkgSnNm: z.string().nullish(),
|
||||
menuPicUserUnqId: z.string().nullish(),
|
||||
menuPicUserUnqIdNm: z.string().nullish(),
|
||||
menuPicDsgnYmd: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type MenuFormData = z.infer<typeof menuFormSchema>;
|
||||
|
||||
export type Menu = Omit<MenuFormData, "iconNm"> & {
|
||||
menuCd: number;
|
||||
iconName?: string;
|
||||
};
|
||||
|
||||
export interface MenuService {
|
||||
getMenuRoles(siteId: string): Promise<MenuItem[]>;
|
||||
getMenuInfo: (menuCd: number) => Promise<Menu>;
|
||||
createMenu: (data: MenuFormData) => Promise<void>;
|
||||
updateMenu: (
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
) => Promise<void>;
|
||||
deleteMenu: (menuCd: number) => Promise<void>;
|
||||
menuTemporaryStorage: (menuCd: number, data: MenuFormData) => Promise<void>;
|
||||
getMenuTemporarilyStoredData: (
|
||||
menuCd: number,
|
||||
) => Promise<MenuTemporarilyStoredData[]>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageableList } from "@/shared/model";
|
||||
import type {
|
||||
MenuHistory,
|
||||
MenuHistorySearchParams,
|
||||
MenuHistoryService,
|
||||
} from "../model/menu-history-interface";
|
||||
|
||||
export class MenuHistoryApiService implements MenuHistoryService {
|
||||
public async getMenuHistoryList(
|
||||
params: MenuHistorySearchParams,
|
||||
): Promise<PageableList<MenuHistory>> {
|
||||
const response = await client.get<PageableList<MenuHistory>>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENU_HISTORY}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async rollbackMenu(hstrySn: number): Promise<void> {
|
||||
await client.post(`${ENDPOINT.CMS_SERVICE.MENU_HISTORY}/rollback`, {
|
||||
hstrySn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const MenuHistoryApi = new MenuHistoryApiService();
|
||||
@@ -1,65 +0,0 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type {
|
||||
Menu,
|
||||
MenuFormData,
|
||||
MenuService,
|
||||
TreeMenu,
|
||||
TreeNode,
|
||||
} from "../model/menu-interface";
|
||||
|
||||
export class MenuApiService implements MenuService {
|
||||
public async getMenuInfo(menuCd: number): Promise<Menu> {
|
||||
const response = await client.get<Menu>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createMenu(data: MenuFormData): Promise<void> {
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenu(
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deleteMenu(menuCd: number): Promise<void> {
|
||||
await client.delete(`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`);
|
||||
}
|
||||
|
||||
public async getTreeMenuList(siteId: string): Promise<TreeMenu[]> {
|
||||
const response = await client.get<TreeMenu[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateTreeMenu(siteId: string, data: TreeNode[]): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenuName(menuCd: number, name: string): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const MenuApi = new MenuApiService();
|
||||
@@ -0,0 +1,33 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type {
|
||||
TreeMenuService,
|
||||
TreeMenu,
|
||||
TreeNode,
|
||||
} from "../model/tree-menu-interface";
|
||||
|
||||
export class TreeMenuApiService implements TreeMenuService {
|
||||
public async getTreeMenuList(siteId: string): Promise<TreeMenu[]> {
|
||||
const response = await client.get<TreeMenu[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateTreeMenu(siteId: string, data: TreeNode[]): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenuName(menuCd: number, name: string): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const TreeMenuApi = new TreeMenuApiService();
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
TreeUserOrganization,
|
||||
TreeUser,
|
||||
} from "@/entities/user-organization";
|
||||
import type { TreeMenu, TreeNode } from "../model/menu-interface";
|
||||
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
|
||||
|
||||
export const generateMenuTreeData = (
|
||||
data: TreeMenu[],
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Checkbox } from "@common/components";
|
||||
import type { MenuHistory } from "./menu-history-interface";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const columns: ColumnDef<MenuHistory>[] = [
|
||||
{
|
||||
id: "select",
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="행 선택"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "chgTypeNm",
|
||||
header: "이력구분",
|
||||
meta: {
|
||||
width: 80,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "menuKornNm",
|
||||
header: "메뉴명",
|
||||
meta: {
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "bchgData",
|
||||
header: "이전 데이터",
|
||||
cell: ({ row }) => {
|
||||
const parsed = JSON.parse(row.original.bchgData);
|
||||
const stringified = JSON.stringify(parsed, null, 2);
|
||||
return <pre className="break-all text-left">{stringified}</pre>;
|
||||
},
|
||||
meta: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "achgData",
|
||||
header: "변경 데이터",
|
||||
cell: ({ row }) => {
|
||||
const parsed = JSON.parse(row.original.achgData);
|
||||
const stringified = JSON.stringify(parsed, null, 2);
|
||||
return <pre className="break-all text-left">{stringified}</pre>;
|
||||
},
|
||||
meta: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "regDt",
|
||||
header: "변경일시",
|
||||
cell: ({ row }) => {
|
||||
return dayjs(row.original.regDt).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "rgtrNm",
|
||||
header: "변경자",
|
||||
meta: {
|
||||
width: 80,
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { PageableParams, PageableList } from "@/shared/model";
|
||||
|
||||
export interface MenuHistorySearchParams extends PageableParams {
|
||||
keyword?: string;
|
||||
searchType?: string;
|
||||
menuCd?: number;
|
||||
chgTypeCd?: string;
|
||||
startDt?: string;
|
||||
endDt?: string;
|
||||
}
|
||||
|
||||
export interface MenuHistory {
|
||||
hstrySn: number;
|
||||
menuCd: number;
|
||||
chgTypeCd: string;
|
||||
chgTypeNm: string;
|
||||
bchgData: string;
|
||||
achgData: string;
|
||||
rgtr: string;
|
||||
regDt: string;
|
||||
rgtrNm: string;
|
||||
menuKornNm: string;
|
||||
}
|
||||
|
||||
export interface MenuHistoryService {
|
||||
getMenuHistoryList: (
|
||||
params: MenuHistorySearchParams,
|
||||
) => Promise<PageableList<MenuHistory>>;
|
||||
rollbackMenu: (hstrySn: number) => Promise<void>;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MENU_TYPE = {
|
||||
EMPTY: "EMPTY",
|
||||
CONTENTS: "CONTENTS",
|
||||
BOARD: "BOARD",
|
||||
INSIDE: "INSIDE",
|
||||
OUTSIDE: "OUTSIDE",
|
||||
BOTTOM: "BOTTOM",
|
||||
} as const;
|
||||
|
||||
export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE];
|
||||
|
||||
export interface TreeNode {
|
||||
menuCd?: number;
|
||||
name?: string;
|
||||
sortSeq?: number;
|
||||
parentId?: number | null;
|
||||
levelNo?: number;
|
||||
icon?: string;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
|
||||
menuKornNm: string;
|
||||
menuEngNm?: string;
|
||||
iconNm?: string;
|
||||
children?: TreeMenu[];
|
||||
urlPath?: string;
|
||||
menuLnkgTypeCd: string;
|
||||
menuLnkgSn?: number;
|
||||
qrcd?: string;
|
||||
useYn: string;
|
||||
otptYn: string;
|
||||
requiredUrlPath?: boolean;
|
||||
}
|
||||
|
||||
export const menuFormSchema = z.object({
|
||||
siteId: z.string(),
|
||||
menuKornNm: z.string(),
|
||||
menuSubKornNm: z.string().nullish(),
|
||||
menuEngNm: z.string().nullish(),
|
||||
menuSubEngNm: z.string().nullish(),
|
||||
levelNo: z.number(),
|
||||
urlPath: z.string().nullish(),
|
||||
iconNm: z.string().nullish(),
|
||||
menuLnkgTypeCd: z.enum([
|
||||
MENU_TYPE.EMPTY,
|
||||
MENU_TYPE.CONTENTS,
|
||||
MENU_TYPE.BOARD,
|
||||
MENU_TYPE.INSIDE,
|
||||
MENU_TYPE.OUTSIDE,
|
||||
MENU_TYPE.BOTTOM,
|
||||
]),
|
||||
menuLnkgSn: z.number().nullish(),
|
||||
otptYn: z.string().default("Y"),
|
||||
prntMenuCd: z.number().nullish(),
|
||||
sortSeq: z.number(),
|
||||
evlUseYn: z.string().default("Y"),
|
||||
qrcd: z.string().nullish(),
|
||||
useYn: z.string().default("Y"),
|
||||
newWndwYn: z.string().nullish(),
|
||||
menuLnkgSnNm: z.string().nullish(),
|
||||
menuPicUserUnqId: z.string().nullish(),
|
||||
menuPicUserUnqIdNm: z.string().nullish(),
|
||||
menuPicDsgnYmd: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type MenuFormData = z.infer<typeof menuFormSchema>;
|
||||
export type Menu = Omit<MenuFormData, "iconNm"> & {
|
||||
menuCd: number;
|
||||
iconName?: string;
|
||||
};
|
||||
|
||||
export interface MenuService {
|
||||
getMenuInfo: (menuCd: number) => Promise<Menu>;
|
||||
createMenu: (data: MenuFormData) => Promise<void>;
|
||||
updateMenu: (
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
) => Promise<void>;
|
||||
deleteMenu: (menuCd: number) => Promise<void>;
|
||||
getTreeMenuList: (siteId: string) => Promise<TreeMenu[]>;
|
||||
updateTreeMenu: (siteId: string, data: TreeNode[]) => Promise<void>;
|
||||
updateMenuName: (menuCd: number, name: string) => Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export interface TreeNode {
|
||||
menuCd?: number;
|
||||
name?: string;
|
||||
sortSeq?: number;
|
||||
parentId?: number | null;
|
||||
levelNo?: number;
|
||||
icon?: string;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
|
||||
menuKornNm: string;
|
||||
menuEngNm?: string;
|
||||
iconNm?: string;
|
||||
children?: TreeMenu[];
|
||||
urlPath?: string;
|
||||
menuLnkgTypeCd: string;
|
||||
menuLnkgSn?: number;
|
||||
qrcd?: string;
|
||||
useYn: string;
|
||||
otptYn: string;
|
||||
requiredUrlPath?: boolean;
|
||||
}
|
||||
|
||||
export interface TreeMenuService {
|
||||
getTreeMenuList: (siteId: string) => Promise<TreeMenu[]>;
|
||||
updateTreeMenu: (siteId: string, data: TreeNode[]) => Promise<void>;
|
||||
updateMenuName: (menuCd: number, name: string) => Promise<void>;
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import { MENU_TYPE, type MenuFormData } from "@/entities/menu";
|
||||
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
|
||||
import { MENU_TYPE, type MenuFormData } from "../model/menu-interface";
|
||||
import { InfoTooltip } from "@/shared/ui/info-tooltip";
|
||||
|
||||
interface BaseMenuFormProps {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { Button } from "@common/components";
|
||||
import { Form } from "@/shared/ui/form";
|
||||
import { MenuApi, menuFormSchema, type MenuFormData } from "@/entities/menu";
|
||||
import { ManagementAuthorityDialog } from "@/entities/menu-authority";
|
||||
import type { MenuFormData } from "../model/menu-interface";
|
||||
import { Form } from "@/shared/ui/form";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { BaseMenuForm } from "./base-menu-form";
|
||||
import { TypeMenuForm } from "./type-menu-form";
|
||||
import { MenuHistoryDialog } from "./menu-history-dialog";
|
||||
|
||||
interface MenuFormProps {
|
||||
form: UseFormReturn<MenuFormData>;
|
||||
@@ -31,6 +35,20 @@ function MenuForm({
|
||||
const isModifyMode = !!menuCd;
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const { data: menuTemporarilyStoredData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_TEMPORARY_STORAGE, menuCd],
|
||||
queryFn: () => MenuApi.getMenuTemporarilyStoredData(menuCd!),
|
||||
enabled: !!menuCd,
|
||||
});
|
||||
|
||||
const { mutate: menuTemporaryStorage } = useMutationWrapper({
|
||||
mutationFn: () => MenuApi.menuTemporaryStorage(menuCd!, form.getValues()),
|
||||
onSuccess: () => {
|
||||
alert("메뉴 임시 저장이 완료되었습니다.");
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: MenuFormData) => {
|
||||
if (isModifyMode) {
|
||||
@@ -58,6 +76,22 @@ function MenuForm({
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuTemporaryStorage = () => {
|
||||
if (!menuCd || !menuFormSchema.parse(form.getValues())) return;
|
||||
menuTemporaryStorage();
|
||||
};
|
||||
|
||||
const handleLoadTemporaryData = () => {
|
||||
if (!menuTemporarilyStoredData?.[0]?.menuTmprStrg) {
|
||||
alert("불러올 임시 저장 데이터가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const storedData = menuTemporarilyStoredData[0].menuTmprStrg;
|
||||
const parsedData = JSON.parse(storedData);
|
||||
form.reset(parsedData);
|
||||
};
|
||||
|
||||
const menuType = form.watch("menuLnkgTypeCd");
|
||||
const isSubmitDisabled = !form.watch("siteId");
|
||||
|
||||
@@ -69,7 +103,10 @@ function MenuForm({
|
||||
<h3 className="text-2xl font-medium text-label">
|
||||
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
|
||||
</h3>
|
||||
<ManagementAuthorityDialog menuCd={menuCd} />
|
||||
<div className="flex items-center gap-2">
|
||||
<MenuHistoryDialog menuCd={menuCd} />
|
||||
<ManagementAuthorityDialog menuCd={menuCd} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<BaseMenuForm form={form} menuCd={menuCd} />
|
||||
@@ -77,6 +114,26 @@ function MenuForm({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
||||
{isModifyMode && (
|
||||
<Button
|
||||
size="lg"
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={handleLoadTemporaryData}
|
||||
>
|
||||
불러오기
|
||||
</Button>
|
||||
)}
|
||||
{isModifyMode && (
|
||||
<Button
|
||||
size="lg"
|
||||
color="gray"
|
||||
type="button"
|
||||
onClick={handleMenuTemporaryStorage}
|
||||
>
|
||||
임시저장
|
||||
</Button>
|
||||
)}
|
||||
<Button size="lg" type="submit" disabled={isSubmitDisabled}>
|
||||
저장
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
type PaginationState,
|
||||
type RowSelectionState,
|
||||
} from "@tanstack/react-table";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Input,
|
||||
Label,
|
||||
} from "@common/components";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
|
||||
import { MenuHistoryApi } from "../api/menu-history-service";
|
||||
import type { MenuHistorySearchParams } from "../model/menu-history-interface";
|
||||
import { columns } from "../model/menu-history-columns";
|
||||
|
||||
type MenuHistoryDialogProps = {
|
||||
menuCd?: number;
|
||||
} & React.ComponentProps<typeof Dialog>;
|
||||
|
||||
function MenuHistoryDialog({ menuCd }: MenuHistoryDialogProps) {
|
||||
const { confirm } = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<MenuHistorySearchParams>({
|
||||
keyword: "",
|
||||
searchType: "menuKornNm",
|
||||
});
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const selectedHistoryId = parseInt(Object.keys(rowSelection)[0] ?? "0");
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_HISTORY, pagination],
|
||||
queryFn: () =>
|
||||
MenuHistoryApi.getMenuHistoryList({
|
||||
...searchParams,
|
||||
menuCd,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
enabled: !!menuCd && open,
|
||||
});
|
||||
|
||||
const { mutateAsync: rollbackMenu } = useMutationWrapper({
|
||||
mutationFn: (hstrySn: number) => MenuHistoryApi.rollbackMenu(hstrySn),
|
||||
onSuccess: () => {
|
||||
alert("메뉴가 성공적으로 복원되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS],
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
});
|
||||
|
||||
const handleRollback = async (hstrySn: number) => {
|
||||
if (!(await confirm("메뉴를 '이전 데이터'로 되돌리시겠습니까?"))) return;
|
||||
await rollbackMenu(hstrySn);
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: { pagination, rowSelection },
|
||||
manualPagination: true,
|
||||
enableRowSelection: true,
|
||||
enableMultiRowSelection: false,
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getRowId: (row) => row.hstrySn.toString(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
query.refetch();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
});
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setRowSelection({});
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button color="white" disabled={!menuCd}>
|
||||
이력보기
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[100rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>메뉴 이력 관리</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSearch}>
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<Label>메뉴명</Label>
|
||||
<Input
|
||||
placeholder="검색어를 입력하세요"
|
||||
value={searchParams.keyword ?? ""}
|
||||
onChange={(e) =>
|
||||
setSearchParams({ ...searchParams, keyword: e.target.value })
|
||||
}
|
||||
className="w-[36rem]"
|
||||
/>
|
||||
<Button type="submit">조회</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="max-h-[56rem] overflow-auto">{tableComponent}</div>
|
||||
|
||||
<DialogFooter className="!justify-center">
|
||||
<DialogClose asChild>
|
||||
<Button size="lg" color="white">
|
||||
닫기
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={() => handleRollback(selectedHistoryId)}
|
||||
disabled={!selectedHistoryId}
|
||||
>
|
||||
되돌리기
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { MenuHistoryDialog };
|
||||
@@ -4,8 +4,8 @@ import type { NodeModel, TreeMethods } from "@minoru/react-dnd-treeview";
|
||||
import { Button, cn } from "@common/components";
|
||||
import { useMutationWrapper } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { MenuApi } from "../api/menu-service";
|
||||
import type { TreeMenu } from "../model/menu-interface";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu } from "../model/tree-menu-interface";
|
||||
import { EditableNode } from "./editable-node";
|
||||
|
||||
type MenuNodeProps = {
|
||||
@@ -37,7 +37,7 @@ function MenuNode({
|
||||
const { mutateAsync: updateMenuName } = useMutationWrapper({
|
||||
mutationFn: (nodeName: string) => {
|
||||
const encodedName = encodeURIComponent(nodeName);
|
||||
return MenuApi.updateMenuName(id as number, encodedName);
|
||||
return TreeMenuApi.updateMenuName(id as number, encodedName);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
|
||||
@@ -14,19 +14,20 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
import { SiteApi } from "@/entities/site";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import type { TreeMenu } from "../model/menu-interface";
|
||||
import { MenuApi } from "../api/menu-service";
|
||||
import {
|
||||
MenuApi,
|
||||
MENU_TYPE,
|
||||
menuFormSchema,
|
||||
type MenuFormData,
|
||||
} from "../model/menu-interface";
|
||||
} from "@/entities/menu";
|
||||
import { SiteApi } from "@/entities/site";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { generateMenuTreeData } from "../lib/utils";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu } from "../model/tree-menu-interface";
|
||||
import { MenuTree } from "./menu-tree";
|
||||
import { MenuForm } from "./menu-form";
|
||||
import { generateMenuTreeData } from "../lib/utils";
|
||||
|
||||
function MenuPage() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -46,7 +47,7 @@ function MenuPage() {
|
||||
|
||||
const { data: menuTreeData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
|
||||
queryFn: () => MenuApi.getTreeMenuList(siteId),
|
||||
queryFn: () => TreeMenuApi.getTreeMenuList(siteId),
|
||||
select: (data) => {
|
||||
const treeData = generateMenuTreeData(data);
|
||||
const defaultSortSeq =
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
import { Button } from "@common/components";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useMutationWrapper } from "@/shared/lib";
|
||||
import { MenuApi } from "../api/menu-service";
|
||||
import type { MenuFormData, TreeMenu, TreeNode } from "../model/menu-interface";
|
||||
import type { MenuFormData } from "@/entities/menu";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
|
||||
import { handleDropNode, handleSelectNode } from "../lib/utils";
|
||||
import { MenuNode } from "./menu-node";
|
||||
import { EditableNode } from "./editable-node";
|
||||
@@ -41,7 +42,7 @@ function MenuTree({
|
||||
|
||||
const { mutate: updateTreeMenu } = useMutationWrapper({
|
||||
mutationFn: (newTree: TreeNode) =>
|
||||
MenuApi.updateTreeMenu(siteId, [newTree]),
|
||||
TreeMenuApi.updateTreeMenu(siteId, [newTree]),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS],
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
import type { BoardPopup } from "@/entities/board";
|
||||
import type { Contents } from "@/entities/content";
|
||||
import type { TreeUser } from "@/entities/user-organization";
|
||||
import type { MenuFormData, MenuType } from "@/entities/menu";
|
||||
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
|
||||
import type { MenuFormData, MenuType } from "../model/menu-interface";
|
||||
import { OrganizationSearchDialog } from "./organization-search-dialog";
|
||||
import { ContentSearchDialog } from "./content-search-dialog";
|
||||
import { BoardSearchDialog } from "./board-search-dialog";
|
||||
|
||||
@@ -140,7 +140,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
}, [laeSeCd, watchDtlInstCd]);
|
||||
|
||||
const enoCdSelectData = useMemo(
|
||||
() => laeSeCd?.filter((code) => code.unit?.startsWith("G2")) ?? [],
|
||||
() => laeSeCd?.filter((code) => code.lwpoCd?.startsWith("G2")) ?? [],
|
||||
[laeSeCd],
|
||||
);
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ export const ENDPOINT = {
|
||||
LOGINLOG: "/user-service/api/v1/login-logs",
|
||||
APILOG: "/user-service/api/v1/api-logs",
|
||||
PRIVACYLOG: "/user-service//api/v1/prvc-inq-logs",
|
||||
|
||||
},
|
||||
CMS_SERVICE: {
|
||||
SITES: "/cms-service/api/v1/sites",
|
||||
@@ -33,6 +32,8 @@ export const ENDPOINT = {
|
||||
MENUS: "/cms-service/api/v1/menus",
|
||||
MENU_ROLES: "/cms-service/api/v1/menu-roles",
|
||||
MENU_AUTHRTS: "/cms-service/api/v1/menu-authrts",
|
||||
MENU_TEMPORARY_STORAGE: "/cms-service/api/v1/menu-tmpr-strg",
|
||||
MENU_HISTORY: "/cms-service/api/v1/menu-hstry",
|
||||
CONTENTS: "/cms-service/api/v1/contents",
|
||||
CONTENTS_DETAILS: "/cms-service/api/v1/contents-details",
|
||||
ORGANIZATIONS: "/cms-service/api/v1/ognzs",
|
||||
|
||||
Reference in New Issue
Block a user