feat: 콘텐츠 메뉴 등록 작업
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
import { client } from "@/shared/api";
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
import type {
|
||||||
|
ContentService,
|
||||||
|
ContentParams,
|
||||||
|
ContentList,
|
||||||
|
} from "../model/content-interface";
|
||||||
|
|
||||||
|
export class ContentApiService implements ContentService {
|
||||||
|
public async getContentList(params: ContentParams): Promise<ContentList> {
|
||||||
|
const response = await client.get<ContentList>(
|
||||||
|
`${ENDPOINT.CMS_SERVICE.CONTENTS}`,
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContentApi = new ContentApiService();
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { PageableList, PageableParams } from "@/shared/model";
|
||||||
|
|
||||||
|
export interface ContentParams extends PageableParams {
|
||||||
|
siteId?: string;
|
||||||
|
menuKornNm?: string;
|
||||||
|
urlPath?: string;
|
||||||
|
useYn?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Content {
|
||||||
|
contsSn: number;
|
||||||
|
contsTtl: string;
|
||||||
|
aprvContsVer: number;
|
||||||
|
useYn: string;
|
||||||
|
rgtr: string;
|
||||||
|
regDt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ContentList = PageableList<Content>;
|
||||||
|
|
||||||
|
export interface ContentService {
|
||||||
|
getContentList(params: ContentParams): Promise<ContentList>;
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const MENU_TYPE = {
|
export const MENU_TYPE = {
|
||||||
EMPTY: "empty",
|
EMPTY: "EMPTY",
|
||||||
CONTENTS: "contents",
|
CONTENTS: "CONTENTS",
|
||||||
BOARD: "board",
|
BOARD: "BOARD",
|
||||||
INSIDE: "inside",
|
INSIDE: "INSIDE",
|
||||||
OUTSIDE: "outside",
|
OUTSIDE: "OUTSIDE",
|
||||||
BOTTOM: "bottom",
|
BOTTOM: "BOTTOM",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE];
|
export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE];
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import { Checkbox } from "@common/components/ui";
|
||||||
|
import type { Content } from "../model/content-interface";
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<Content>();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
columnHelper.accessor((_, index) => index + 1, {
|
||||||
|
id: "no",
|
||||||
|
header: "NO",
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("contsTtl", {
|
||||||
|
id: "contsTtl",
|
||||||
|
header: "콘텐츠명",
|
||||||
|
}),
|
||||||
|
// TODO: 최종수정자, 최종수정일, 승인일시 추가
|
||||||
|
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,73 @@
|
|||||||
|
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,
|
||||||
|
} from "@common/components/ui";
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
import { ContentApi } from "../api/content-service";
|
||||||
|
import type { Content } from "../model/content-interface";
|
||||||
|
import { columns } from "../model/table-colums";
|
||||||
|
|
||||||
|
type ContentDialogProps = {
|
||||||
|
handleContentSelect: (content: Content) => void;
|
||||||
|
} & React.ComponentProps<typeof Dialog>;
|
||||||
|
|
||||||
|
function ContentDialog({ handleContentSelect, ...props }: ContentDialogProps) {
|
||||||
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
|
|
||||||
|
const { data: contentList } = useQuery({
|
||||||
|
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS],
|
||||||
|
queryFn: () =>
|
||||||
|
ContentApi.getContentList({ useYn: "Y", page: 0, size: 100 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: contentList?.content ?? [],
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
rowSelection,
|
||||||
|
},
|
||||||
|
enableMultiRowSelection: false,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const contentData = table.getSelectedRowModel().rows[0]
|
||||||
|
?.original as Content;
|
||||||
|
if (!contentData) return;
|
||||||
|
handleContentSelect(contentData);
|
||||||
|
}, [table.getSelectedRowModel().rows]);
|
||||||
|
|
||||||
|
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="mt-4 h-[40rem] overflow-y-auto">
|
||||||
|
<DataTable table={table} />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ContentDialog };
|
||||||
@@ -16,9 +16,16 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/shared/ui/form";
|
} from "@/shared/ui/form";
|
||||||
import type { MenuFormData } from "../model/menu-interface";
|
import type { MenuFormData } from "../model/menu-interface";
|
||||||
|
import type { Content } from "../model/content-interface";
|
||||||
import { BaseTypeMenuForm } from "./base-type-menu-form";
|
import { BaseTypeMenuForm } from "./base-type-menu-form";
|
||||||
|
import { ContentDialog } from "./content-dialog";
|
||||||
|
|
||||||
function ContentMenuForm({ form }: { form: UseFormReturn<MenuFormData> }) {
|
function ContentMenuForm({ form }: { form: UseFormReturn<MenuFormData> }) {
|
||||||
|
const handleContentSelect = (content: Content) => {
|
||||||
|
form.setValue("menuLnkgSn", content.contsSn);
|
||||||
|
form.setValue("menuLnkgSnNm", content.contsTtl);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table variant="secondary">
|
<Table variant="secondary">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
@@ -29,42 +36,15 @@ function ContentMenuForm({ form }: { form: UseFormReturn<MenuFormData> }) {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>콘텐츠</TableHead>
|
<TableHead>콘텐츠</TableHead>
|
||||||
<TableCell className="border">
|
<TableCell className="border">
|
||||||
<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">
|
<div className="flex items-center gap-2">
|
||||||
{form.watch("menuLnkgSn")}
|
<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>
|
||||||
|
<ContentDialog handleContentSelect={handleContentSelect} />
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
{/* TODO: 연결 형태 추가 */}
|
||||||
<TableHead>연결 형태</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="evlUseYn"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<RadioGroup
|
|
||||||
value={field.value ?? ""}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="self" id="self" />
|
|
||||||
<Label htmlFor="self">현재창</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="blank" id="blank" />
|
|
||||||
<Label htmlFor="blank">새창</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>메뉴 평가 사용 여부</TableHead>
|
<TableHead>메뉴 평가 사용 여부</TableHead>
|
||||||
<TableCell className="border">
|
<TableCell className="border">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const ENDPOINT = {
|
|||||||
IPS: "/cms-service/api/v1/site-ips",
|
IPS: "/cms-service/api/v1/site-ips",
|
||||||
DOMAINS: "/cms-service/api/v1/site-dmns",
|
DOMAINS: "/cms-service/api/v1/site-dmns",
|
||||||
MENUS: "/cms-service/api/v1/menus",
|
MENUS: "/cms-service/api/v1/menus",
|
||||||
|
CONTENTS: "/cms-service/api/v1/contents",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -79,11 +79,7 @@ const TableHead = React.forwardRef<
|
|||||||
HTMLTableCellElement,
|
HTMLTableCellElement,
|
||||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<th
|
<th ref={ref} className={cn("align-middle", className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("align-middle [&:has([role=checkbox])]:pr-0", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
));
|
||||||
TableHead.displayName = "TableHead";
|
TableHead.displayName = "TableHead";
|
||||||
|
|
||||||
@@ -93,10 +89,7 @@ const TableCell = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<td
|
<td
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn("align-middle text-foreground", className)}
|
||||||
"align-middle text-foreground [&:has([role=checkbox])]:pr-0",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user