feat: 게시판 메뉴 등록 작업
This commit is contained in:
@@ -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();
|
||||
@@ -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,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user