269 lines
7.6 KiB
TypeScript
269 lines
7.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import type { NodeModel } from "@minoru/react-dnd-treeview";
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import {
|
|
Button,
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@common/components";
|
|
import {
|
|
MenuApi,
|
|
MENU_TYPE,
|
|
menuFormSchema,
|
|
type MenuFormData,
|
|
} from "@/entities/menu";
|
|
import { SiteApi } from "@/entities/site";
|
|
import { TreeMenuApi, type TreeMenu } from "@/entities/tree-menu";
|
|
import { ENDPOINT } from "@/shared/config";
|
|
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
|
import { generateMenuTreeData } from "../lib/utils";
|
|
import { MenuTree } from "./menu-tree";
|
|
import { MenuForm } from "./menu-form";
|
|
|
|
function MenuPage() {
|
|
const queryClient = useQueryClient();
|
|
const alert = useAlertStore((state) => state.setMessage);
|
|
const { confirm } = useConfirm();
|
|
|
|
const [siteId, setSiteId] = useState("");
|
|
const [selectedMenu, setSelectedMenu] = useState<TreeMenu | null>(null);
|
|
const [additionalNode, setAdditionalNode] =
|
|
useState<NodeModel<TreeMenu> | null>(null);
|
|
|
|
const { data: menuInfo } = useQuery({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, selectedMenu?.menuCd],
|
|
queryFn: () => MenuApi.getMenuInfo(selectedMenu?.menuCd ?? 0),
|
|
enabled: !!selectedMenu?.menuCd,
|
|
});
|
|
|
|
const { data: menuTreeData } = useQuery({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
|
|
queryFn: () => TreeMenuApi.getTreeMenuList(siteId),
|
|
select: (data) => {
|
|
const treeData = generateMenuTreeData(data);
|
|
const defaultSortSeq =
|
|
treeData.filter((node) => node.data?.levelNo === 1).length + 1;
|
|
form.setValue("sortSeq", defaultSortSeq);
|
|
|
|
return treeData;
|
|
},
|
|
enabled: !!siteId,
|
|
});
|
|
|
|
const { data: options } = useQuery({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.SITES],
|
|
queryFn: () => SiteApi.getSiteList(),
|
|
select: (data) =>
|
|
data.content.map((site) => ({
|
|
label: site.siteNm,
|
|
value: site.siteId,
|
|
})),
|
|
});
|
|
|
|
const { mutateAsync: createMenu } = useMutationWrapper({
|
|
mutationFn: (data: MenuFormData) => MenuApi.createMenu(data),
|
|
onSuccess: () => {
|
|
alert("저장되었습니다.");
|
|
queryClient.invalidateQueries({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
|
|
});
|
|
queryClient.invalidateQueries({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
|
|
});
|
|
},
|
|
});
|
|
|
|
const { mutate: updateMenu } = useMutationWrapper({
|
|
mutationFn: ({
|
|
menuCd,
|
|
data,
|
|
}: {
|
|
menuCd: number;
|
|
data: Omit<MenuFormData, "siteId">;
|
|
}) => MenuApi.updateMenu(menuCd, data),
|
|
onSuccess: () => {
|
|
alert("수정되었습니다.");
|
|
queryClient.invalidateQueries({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
|
|
});
|
|
queryClient.invalidateQueries({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
|
|
});
|
|
},
|
|
});
|
|
|
|
const { mutate: deleteMenu } = useMutationWrapper({
|
|
mutationFn: (menuCd: number) => MenuApi.deleteMenu(menuCd),
|
|
onSuccess: () => {
|
|
alert("삭제되었습니다.");
|
|
queryClient.invalidateQueries({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
|
|
});
|
|
queryClient.invalidateQueries({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
|
|
});
|
|
form.reset();
|
|
},
|
|
});
|
|
|
|
const form = useForm<MenuFormData>({
|
|
resolver: zodResolver(menuFormSchema),
|
|
defaultValues: {
|
|
menuLnkgTypeCd: MENU_TYPE.EMPTY,
|
|
},
|
|
values: {
|
|
...menuInfo,
|
|
siteId,
|
|
levelNo: menuInfo?.levelNo ?? 1,
|
|
sortSeq: menuInfo?.sortSeq ?? 1,
|
|
menuKornNm: menuInfo?.menuKornNm ?? "",
|
|
menuLnkgTypeCd: menuInfo?.menuLnkgTypeCd ?? MENU_TYPE.EMPTY,
|
|
iconNm: menuInfo?.iconName,
|
|
otptYn: menuInfo?.otptYn ?? "Y",
|
|
evlUseYn: menuInfo?.evlUseYn ?? "N",
|
|
qrcd: menuInfo?.qrcd ?? "N",
|
|
useYn: menuInfo?.useYn ?? "Y",
|
|
newWndwYn: menuInfo?.newWndwYn ?? "N",
|
|
},
|
|
});
|
|
|
|
const additionalNodeForm = useForm<MenuFormData>({
|
|
resolver: zodResolver(menuFormSchema),
|
|
defaultValues: {
|
|
menuLnkgTypeCd: MENU_TYPE.EMPTY,
|
|
levelNo: 1,
|
|
sortSeq: 1,
|
|
otptYn: "Y",
|
|
evlUseYn: "Y",
|
|
useYn: "Y",
|
|
},
|
|
});
|
|
|
|
const handleAdditionalNodeSave = async () => {
|
|
const prntMenuCd = !additionalNode?.parent
|
|
? null
|
|
: (additionalNode?.parent as number);
|
|
|
|
await createMenu({
|
|
...additionalNodeForm.getValues(),
|
|
prntMenuCd,
|
|
});
|
|
resetAdditionalNode();
|
|
};
|
|
|
|
const resetAdditionalNode = () => {
|
|
additionalNodeForm.setValue("menuKornNm", "");
|
|
setAdditionalNode(null);
|
|
};
|
|
|
|
const handleSiteIdChange = (value: string) => {
|
|
form.setValue("siteId", value);
|
|
additionalNodeForm.setValue("siteId", value);
|
|
|
|
setSiteId(value);
|
|
setSelectedMenu(null);
|
|
};
|
|
|
|
const handleNodeSelect = (node: NodeModel<TreeMenu>) => {
|
|
if (!node.data) return;
|
|
if (node.data.menuCd === selectedMenu?.menuCd) {
|
|
setSelectedMenu(null);
|
|
additionalNodeForm.setValue("levelNo", 1);
|
|
return;
|
|
}
|
|
setSelectedMenu(node.data);
|
|
additionalNodeForm.setValue("levelNo", (node.data.levelNo ?? 0) + 1);
|
|
};
|
|
|
|
const handleNodeDelete = async () => {
|
|
if (await confirm("삭제하시겠습니까?")) {
|
|
deleteMenu(selectedMenu!.menuCd!);
|
|
}
|
|
};
|
|
|
|
const handleAddMenu = () => {
|
|
if (additionalNode) return;
|
|
setAdditionalNode({
|
|
id: -1,
|
|
parent: selectedMenu?.menuCd ?? 0,
|
|
text: "",
|
|
});
|
|
};
|
|
|
|
const tree = additionalNode
|
|
? [...(additionalNode ? [additionalNode] : []), ...(menuTreeData ?? [])]
|
|
: (menuTreeData ?? []);
|
|
|
|
return (
|
|
<div className="card !mb-0 flex !min-h-[calc(100vh-16.8rem)] gap-2">
|
|
<div className="card card-border w-[24rem]">
|
|
<Select value={siteId} onValueChange={handleSiteIdChange}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="선택하세요" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{options?.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{siteId && (
|
|
<>
|
|
<div className="my-4 flex gap-2">
|
|
<Button
|
|
size="sm"
|
|
color="primary"
|
|
className="flex-1"
|
|
onClick={handleAddMenu}
|
|
>
|
|
메뉴 추가
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
color="red"
|
|
className="flex-1"
|
|
onClick={handleNodeDelete}
|
|
disabled={!selectedMenu}
|
|
>
|
|
메뉴 삭제
|
|
</Button>
|
|
</div>
|
|
<div className="card card-border !mx-0">
|
|
<MenuTree
|
|
tree={tree}
|
|
siteId={siteId}
|
|
selectedMenu={selectedMenu}
|
|
additionalNodeForm={additionalNodeForm}
|
|
onSelectNode={handleNodeSelect}
|
|
onAdditionalNodeSave={handleAdditionalNodeSave}
|
|
onAdditionalNodeCancel={resetAdditionalNode}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
<div className="card card-border flex-1">
|
|
<MenuForm
|
|
form={form}
|
|
menuCd={selectedMenu?.menuCd}
|
|
handleCreate={createMenu}
|
|
handleUpdate={updateMenu}
|
|
handleDelete={deleteMenu}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export { MenuPage };
|