Files
frontend/apps/cs/src/pages/menu/ui/menu-page.tsx
T

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 };