feat: 콘텐츠 관리 목록 페이지 작업

This commit is contained in:
2025-04-15 17:09:08 +09:00
parent 155557f6ae
commit d2386d1efd
13 changed files with 271 additions and 84 deletions
@@ -0,0 +1,2 @@
import { ContentsPage } from "@/pages/contents";
export default ContentsPage;
@@ -0,0 +1,51 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
ContentsService,
ContentsParams,
ContentsList,
Contents,
ContentsFormData,
} from "../model/contents-interface";
export class ContentsApiService implements ContentsService {
public async getContentsList(params: ContentsParams): Promise<ContentsList> {
const response = await client.get<ContentsList>(
`${ENDPOINT.CMS_SERVICE.CONTENTS}`,
{ params },
);
return response.data;
}
public async getContentsInfo(contsSn: number): Promise<Contents> {
const response = await client.get<Contents>(
`${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`,
);
return response.data;
}
public async createContents(data: ContentsFormData): Promise<void> {
const response = await client.post<void>(
`${ENDPOINT.CMS_SERVICE.CONTENTS}`,
data,
);
return response.data;
}
public async updateContents(
contsSn: number,
data: ContentsFormData,
): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`,
data,
);
return response.data;
}
public async deleteContents(contsSn: number): Promise<void> {
await client.delete(`${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`);
}
}
export const ContentsApi = new ContentsApiService();
+2
View File
@@ -0,0 +1,2 @@
export { ContentsApi } from "./api/contents-service";
export type { Contents, ContentsParams } from "./model/contents-interface";
@@ -0,0 +1,37 @@
import { z } from "zod";
import type { PageableList, PageableParams } from "@/shared/model";
export interface ContentsParams extends PageableParams {
contsTtl?: string;
aprvContsVer?: number;
useYn?: string;
}
export const contentsSchema = z.object({
contsSn: z.number(),
contsTtl: z.string(),
aprvContsVer: z.number(),
useYn: z.string(),
regDt: z.string(),
mdfcnDt: z.string(),
rgTrNm: z.string(),
mdfrNm: z.string(),
});
export const contentsFormSchema = z.object({
contsTtl: z.string(),
useYn: z.string(),
rgtr: z.string().nullish(),
});
export type Contents = z.infer<typeof contentsSchema>;
export type ContentsFormData = z.infer<typeof contentsFormSchema>;
export type ContentsList = PageableList<Contents>;
export interface ContentsService {
getContentsList(params: ContentsParams): Promise<ContentsList>;
getContentsInfo(contsSn: number): Promise<Contents>;
createContents(data: ContentsFormData): Promise<void>;
updateContents(contsSn: number, data: ContentsFormData): Promise<void>;
deleteContents(contsSn: number): Promise<void>;
}
-6
View File
@@ -1,6 +0,0 @@
export { GroupApi } from "./api/group-api";
export {
type GroupParams,
type Group,
type GroupFormData,
} from "./model/types";
-29
View File
@@ -1,29 +0,0 @@
export interface Group {
groupId: string;
groupNm: string;
groupSeCd: string;
groupExpln: string;
delYn: string;
regDt: string;
rgtr: string;
mdfcnDt: string | null;
mdfr: string | null;
authrtId: string;
siteId: string | null;
}
export interface GroupParams {
groupNm?: string;
groupSeCd?: string;
siteId?: string;
}
export interface GroupFormData {
groupId: string;
groupNm: string;
groupSeCd: string;
groupExpln: string;
delYn: string;
authrtId: string;
siteId: string;
}
+1
View File
@@ -0,0 +1 @@
export { ContentsPage } from "./ui/contents-page";
@@ -0,0 +1,47 @@
import dayjs from "dayjs";
import { type ColumnDef } from "@tanstack/react-table";
import type { Contents } from "@/entities/contents";
export const columns: ColumnDef<Contents>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
},
{
accessorKey: "contsTtl",
header: "콘텐츠명",
},
{
accessorKey: "aprvContsVer",
header: "승인여부",
},
{
accessorKey: "rgTrNm",
header: "승인자",
},
{
accessorKey: "regDt",
header: "승인일자",
cell: ({ row }) => {
const date = dayjs(row.original.regDt);
return date.format("YYYY-MM-DD");
},
},
{
accessorKey: "useYn",
header: "사용여부",
},
{
accessorKey: "mdfrNm",
header: "최종수정자",
},
{
accessorKey: "mdfcnDt",
header: "최종수정일",
cell: ({ row }) => {
const date = dayjs(row.original.mdfcnDt);
return date.format("YYYY-MM-DD");
},
},
];
@@ -0,0 +1,100 @@
"use client";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import {
ContentsApi,
type Contents,
type ContentsParams,
} from "@/entities/contents";
import { ENDPOINT } from "@/shared/config";
import { columns } from "../model/table-columns";
function ContentsPage() {
const router = useRouter();
const [contentsQuery, setContentsQuery] = useState<ContentsParams>({
contsTtl: "",
});
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 5,
});
const { data: contentsList, refetch } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS],
queryFn: () =>
ContentsApi.getContentsList({
...contentsQuery,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
placeholderData: keepPreviousData,
enabled: false,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
};
const handleRowClick = (row: Contents) => {
router.push(`/contents/register/${row.contsSn}`);
};
const table = useReactTable({
data: contentsList?.content ?? [],
columns,
rowCount: contentsList?.totalElements ?? 0,
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
});
useEffect(() => {
refetch();
}, [pagination]);
return (
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label htmlFor="" className="mr-2 text-label">
콘텐츠명
</Label>
<Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={contentsQuery.contsTtl}
onChange={(e) => setContentsQuery({ contsTtl: e.target.value })}
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
<Button onClick={() => router.push("/contents/register")}>
등록
</Button>
</div>
<div className="card !m-[1.2rem] h-[29.2rem] overflow-auto !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
</div>
</div>
);
}
export { ContentsPage };
@@ -1,19 +0,0 @@
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();
@@ -1,22 +0,0 @@
import type { PageableList, PageableParams } from "@/shared/model";
export interface ContentParams extends PageableParams {
contsTtl?: string;
aprvContsVer?: number;
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,8 +1,9 @@
import dayjs from "dayjs";
import type { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@common/components/ui";
import type { Content } from "./content-interface";
import type { Contents } from "@/entities/contents";
export const columns: ColumnDef<Content>[] = [
export const columns: ColumnDef<Contents>[] = [
{
id: "no",
header: "NO",
@@ -13,7 +14,30 @@ export const columns: ColumnDef<Content>[] = [
header: "콘텐츠명",
cell: ({ row }) => row.original.contsTtl,
},
// TODO: 최종수정자, 최종수정일, 승인일시 추가
{
accessorKey: "rgTrNm",
header: "승인자",
},
{
accessorKey: "regDt",
header: "승인일자",
cell: ({ row }) => {
const date = dayjs(row.original.regDt);
return date.format("YYYY-MM-DD");
},
},
{
accessorKey: "mdfrNm",
header: "최종수정자",
},
{
accessorKey: "mdfcnDt",
header: "최종수정일",
cell: ({ row }) => {
const date = dayjs(row.original.mdfcnDt);
return date.format("YYYY-MM-DD");
},
},
{
id: "select",
header: "선택",
@@ -16,12 +16,11 @@ import {
Input,
} from "@common/components/ui";
import { ENDPOINT } from "@/shared/config";
import { ContentApi } from "../api/content-service";
import type { Content } from "../model/content-interface";
import { ContentsApi, type Contents } from "@/entities/contents";
import { columns } from "../model/content-table-colums";
type ContentSearchDialogProps = {
handleContentSelect: (content: Content) => void;
handleContentSelect: (content: Contents) => void;
} & React.ComponentProps<typeof Dialog>;
function ContentSearchDialog({
@@ -33,7 +32,7 @@ function ContentSearchDialog({
const { data: contentList, refetch } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS],
queryFn: () => ContentApi.getContentList({ contsTtl }),
queryFn: () => ContentsApi.getContentsList({ contsTtl }),
enabled: false,
});
@@ -50,7 +49,7 @@ function ContentSearchDialog({
useEffect(() => {
const contentData = table.getSelectedRowModel().rows[0]
?.original as Content;
?.original as Contents;
if (!contentData) return;
handleContentSelect(contentData);
}, [table.getSelectedRowModel().rows]);