feat: 게시판 메뉴 등록 작업

This commit is contained in:
2025-03-28 18:17:13 +09:00
parent 701d042d4f
commit cd455fa343
7 changed files with 225 additions and 2 deletions
@@ -0,0 +1,21 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
BoardPopupList,
BoardPopupParams,
BoardService,
} from "../model/board-interface";
export class BoardApiService implements BoardService {
public async getBoardPopupList(
params: BoardPopupParams,
): Promise<BoardPopupList> {
const response = await client.get<BoardPopupList>(
`${ENDPOINT.BOARD_SERVICE.BBS}`,
{ params },
);
return response.data;
}
}
export const BoardApi = new BoardApiService();
+2
View File
@@ -0,0 +1,2 @@
export { BoardApi } from "./api/board-service";
export { type BoardPopup } from "./model/board-interface";
@@ -0,0 +1,42 @@
import type { PageableList, PageableParams } from "@/shared/model";
export const BOARD_TYPE = {
BBSNORMAL: "BBSNORMAL",
BBSFAQ: "BBSFAQ",
BBSIMAGE: "BBSIMAGE",
BBSMOVIE: "BBSMOVIE",
} as const;
export type BoardType = (typeof BOARD_TYPE)[keyof typeof BOARD_TYPE];
export interface BoardPopupParams extends PageableParams {
bbsNm?: string;
}
export interface BoardPopup {
bbsId: number;
bbsTypeCd: BoardType;
bbsTypeNm: string;
bbsNm: string;
bbsExpln: string;
rfrncUseYn: string;
atflUseYn: string;
atflMaxCnt: number;
atflMaxFileSz: number;
ansUseYn: string;
cmntUseYn: string;
dgstfnEvlUseYn: string;
smsUseYn: string;
upndFixUseYn: string;
pstgPrdUseYn: string;
popupUseYn: string;
delYn: string;
regDt: string;
pstCnt: number;
}
export type BoardPopupList = PageableList<BoardPopup>;
export interface BoardService {
getBoardPopupList: (params: BoardPopupParams) => Promise<BoardPopupList>;
}
@@ -0,0 +1,43 @@
import { createColumnHelper } from "@tanstack/react-table";
import { Checkbox } from "@common/components/ui";
import type { BoardPopup } from "@/entities/board";
const columnHelper = createColumnHelper<BoardPopup>();
const columns = [
columnHelper.accessor((_, index) => index + 1, {
id: "no",
header: "NO",
}),
columnHelper.accessor("bbsNm", {
id: "bbsNm",
header: "게시판명",
}),
columnHelper.accessor("bbsTypeNm", {
id: "bbsTypeNm",
header: "게시판유형",
}),
columnHelper.accessor("regDt", {
id: "regDt",
header: "최초생성일",
}),
columnHelper.accessor("pstCnt", {
id: "pstCnt",
header: "게시물건수",
}),
columnHelper.display({
id: "select",
header: "선택",
cell: ({ row }) => {
return (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={row.getToggleSelectedHandler()}
className="mx-4 flex justify-center"
/>
);
},
}),
];
export { columns };
@@ -0,0 +1,95 @@
import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import {
Button,
DataTable,
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
Input,
} from "@common/components/ui";
import { ENDPOINT } from "@/shared/config";
import { BoardApi, type BoardPopup } from "@/entities/board";
import { columns } from "../model/board-table-columns";
type BoardSearchDialogProps = {
handleBoardSelect: (board: BoardPopup) => void;
} & React.ComponentProps<typeof Dialog>;
function BoardSearchDialog({
handleBoardSelect,
...props
}: BoardSearchDialogProps) {
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [bbsNm, setBbsNm] = useState("");
const { data: boardList, refetch } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.BBS],
queryFn: () => BoardApi.getBoardPopupList({ bbsNm }),
enabled: false,
});
const table = useReactTable({
data: boardList?.content ?? [],
columns,
state: {
rowSelection,
},
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
useEffect(() => {
const boardData = table.getSelectedRowModel().rows[0]
?.original as BoardPopup;
if (!boardData) return;
handleBoardSelect(boardData);
}, [table.getSelectedRowModel().rows]);
useEffect(() => {
refetch();
}, []);
return (
<Dialog {...props}>
<DialogTrigger asChild>
<Button className="gap-3">
<i className="diveicon di-search" />
조회
</Button>
</DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>게시판 조회</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4">
<div className="card card-border !m-0 flex w-full items-center gap-2">
<Input
placeholder="검색어를 입력하세요."
className="!text-[1.3rem]"
value={bbsNm}
onChange={(e) => setBbsNm(e.target.value)}
/>
<Button onClick={() => refetch()}>
<i className="diveicon di-search" />
조회
</Button>
</div>
<div className="h-[40rem] w-full overflow-y-auto">
<DataTable table={table} />
</div>
</div>
</DialogContent>
</Dialog>
);
}
export { BoardSearchDialog };
+19 -2
View File
@@ -19,11 +19,13 @@ import {
FormField,
FormMessage,
} from "@/shared/ui/form";
import type { BoardPopup } from "@/entities/board";
import type { Content } from "../model/content-interface";
import type { User } from "../model/organization-interface";
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";
type TypeMenuFormProps = {
form: UseFormReturn<MenuFormData>;
@@ -45,6 +47,11 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
form.setValue("menuPicUserUnqIdNm", user.userNm);
};
const handleBoardSelect = (board: BoardPopup) => {
form.setValue("menuLnkgSn", board.bbsId);
form.setValue("menuLnkgSnNm", board.bbsNm);
};
const handleDateSelect = (date: Date | undefined) => {
if (!date) return;
form.setValue("menuPicDsgnYmd", dayjs(date).format("YYYYMMDD"));
@@ -63,6 +70,8 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
<TableCell className="border">
<div className="flex items-center gap-2">
<div className="h-[3.2rem] w-1/2 cursor-not-allowed overflow-hidden whitespace-nowrap break-keep rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{!form.watch("menuLnkgSnNm") &&
"메뉴에 표시될 콘텐츠를 조회 후 선택하십시오."}
{form.watch("menuLnkgSnNm")}
</div>
<ContentSearchDialog
@@ -76,8 +85,13 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
<TableRow>
<TableHead>게시판</TableHead>
<TableCell className="border">
<div className="h-[3.2rem] w-1/2 cursor-not-allowed overflow-hidden whitespace-nowrap break-keep rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{form.watch("menuLnkgSnNm")}
<div className="flex items-center gap-2">
<div className="h-[3.2rem] w-1/2 cursor-not-allowed overflow-hidden whitespace-nowrap break-keep rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{!form.watch("menuLnkgSnNm") &&
"메뉴에 표시될 게시판을 조회 후 선택하십시오."}
{form.watch("menuLnkgSnNm")}
</div>
<BoardSearchDialog handleBoardSelect={handleBoardSelect} />
</div>
</TableCell>
</TableRow>
@@ -180,6 +194,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
<TableCell className="border">
<div className="flex gap-2">
<div className="h-[3.2rem] w-[21.5rem] cursor-not-allowed rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{!form.watch("iconNm") && "아이콘을 조회 후 선택하십시오."}
{form.watch("iconNm")}
</div>
<IconFinder handleClick={handleIconClick} />
@@ -291,6 +306,8 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<div className="h-[3.2rem] w-[21.5rem] cursor-not-allowed rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{!form.watch("menuPicUserUnqIdNm") &&
"메뉴 담당자를 조회 후 선택하십시오."}
{form.watch("menuPicUserUnqIdNm")}
</div>
<OrganizationSearchDialog
+3
View File
@@ -17,6 +17,9 @@ export const ENDPOINT = {
CONTENTS: "/cms-service/api/v1/contents",
USER_OGNZ: "/cms-service/api/v1/user-ognz",
},
BOARD_SERVICE: {
BBS: "/board-service/api/v1/bbs",
},
};
export const JWT_EXPIRATION_TIME = 1800000;