Files
frontend/apps/cs/src/pages/menu/ui/menu-page.tsx
T
2025-03-31 18:03:48 +09:00

267 lines
7.7 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 { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
Button,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components/ui";
import { SiteApi } from "@/entities/site";
import { ENDPOINT } from "@/shared/config";
import { PageLayout } from "@/shared/ui/page-layout";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { useAlertStore } from "@/shared/lib/use-alert-store";
import type { TreeMenu } from "../model/menu-interface";
import { MenuApi } from "../api/menu-service";
import {
MENU_TYPE,
menuFormSchema,
type MenuFormData,
} from "../model/menu-interface";
import { MenuTree } from "./menu-tree";
import { MenuForm } from "./menu-form";
import { generateMenuTreeData } from "../lib/utils";
function MenuPage() {
const queryClient = useQueryClient();
const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm();
const [siteId, setSiteId] = useState("");
const [onConfirm, setOnConfirm] = useState(false);
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: treeData } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
queryFn: () => MenuApi.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 } = useMutation({
mutationFn: (data: MenuFormData) => MenuApi.createMenu(data),
onSuccess: () => {
alert("저장되었습니다.");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
});
},
});
const { mutate: modifyMenu } = useMutation({
mutationFn: ({
menuCd,
data,
}: {
menuCd: number;
data: Omit<MenuFormData, "siteId">;
}) => MenuApi.modifyMenu(menuCd, data),
onSuccess: () => {
alert("수정되었습니다.");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
});
},
});
const { mutate: deleteMenu } = useMutation({
mutationFn: (menuCd: number) => MenuApi.deleteMenu(menuCd),
onSuccess: () => {
alert("삭제되었습니다.");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
});
},
});
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 ?? "Y",
useYn: menuInfo?.useYn ?? "Y",
},
});
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 () => {
setOnConfirm(true);
if (await confirm("삭제하시겠습니까?")) {
setOnConfirm(false);
deleteMenu(selectedMenu!.menuCd!);
}
};
const handleAddMenu = () => {
if (additionalNode) return;
setAdditionalNode({
id: -1,
parent: selectedMenu?.menuCd ?? 0,
text: "",
});
};
const tree = additionalNode
? [...(additionalNode ? [additionalNode] : []), ...(treeData ?? [])]
: (treeData ?? []);
return (
<>
<PageLayout>
<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}
handleModify={modifyMenu}
handleDelete={deleteMenu}
/>
</div>
</div>
</PageLayout>
<ConfirmDialog open={onConfirm} />
</>
);
}
export { MenuPage };