diff --git a/apps/cs/app/(with-base-layout)/terms/page.tsx b/apps/cs/app/(with-base-layout)/terms/page.tsx new file mode 100644 index 000000000..47f4c8e02 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/terms/page.tsx @@ -0,0 +1,2 @@ +import { TermsPage } from "@/pages/terms"; +export default TermsPage; diff --git a/apps/cs/app/(with-base-layout)/terms/register/[sequential]/page.tsx b/apps/cs/app/(with-base-layout)/terms/register/[sequential]/page.tsx new file mode 100644 index 000000000..8ff8b2010 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/terms/register/[sequential]/page.tsx @@ -0,0 +1,10 @@ +import { ModifyPage as VModifyPage } from "@/pages/terms"; + +export default async function ModifyPage({ + params, +}: { + params: Promise<{ sequential: string }>; +}) { + const { sequential } = await params; + return ; +} diff --git a/apps/cs/app/(with-base-layout)/terms/register/page.tsx b/apps/cs/app/(with-base-layout)/terms/register/page.tsx new file mode 100644 index 000000000..997fc119a --- /dev/null +++ b/apps/cs/app/(with-base-layout)/terms/register/page.tsx @@ -0,0 +1,2 @@ +import { CreatePage } from "@/pages/terms"; +export default CreatePage; diff --git a/apps/cs/src/entities/site/api/site-service.ts b/apps/cs/src/entities/site/api/site-service.ts index 68c520093..a5d9d7cd6 100644 --- a/apps/cs/src/entities/site/api/site-service.ts +++ b/apps/cs/src/entities/site/api/site-service.ts @@ -44,10 +44,7 @@ export class SiteApiService implements SiteService { } public async deleteSite(siteId: string): Promise { - const response = await client.delete( - `${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`, - ); - return response.data; + await client.delete(`${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`); } } diff --git a/apps/cs/src/pages/banner/ui/banner-page.tsx b/apps/cs/src/pages/banner/ui/banner-page.tsx index 0efc89602..3f0cf5242 100644 --- a/apps/cs/src/pages/banner/ui/banner-page.tsx +++ b/apps/cs/src/pages/banner/ui/banner-page.tsx @@ -94,10 +94,6 @@ function BannerPage() { setPostingTargets(postingTargets); }, [rowSelection]); - useEffect(() => { - refetch(); - }, []); - useEffect(() => { refetch(); }, [pagination]); diff --git a/apps/cs/src/pages/image/ui/image-page.tsx b/apps/cs/src/pages/image/ui/image-page.tsx index 18d2fa2fb..3571a4a1f 100644 --- a/apps/cs/src/pages/image/ui/image-page.tsx +++ b/apps/cs/src/pages/image/ui/image-page.tsx @@ -83,10 +83,6 @@ function ImagePage() { setPostingTargets(postingTargets); }, [rowSelection]); - useEffect(() => { - refetch(); - }, []); - useEffect(() => { refetch(); }, [pagination]); diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index 7598c2abb..dbb67cb3e 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -36,10 +36,7 @@ export class MenuApiService implements MenuService { } public async deleteMenu(menuCd: number): Promise { - const response = await client.delete( - `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, - ); - return response.data; + await client.delete(`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`); } public async getTreeMenuList(siteId: string): Promise { diff --git a/apps/cs/src/pages/site/ui/site-page.tsx b/apps/cs/src/pages/site/ui/site-page.tsx index 0d6ff4a82..9ffd1294e 100644 --- a/apps/cs/src/pages/site/ui/site-page.tsx +++ b/apps/cs/src/pages/site/ui/site-page.tsx @@ -59,10 +59,6 @@ function SitePage() { manualPagination: true, }); - useEffect(() => { - refetch(); - }, []); - useEffect(() => { refetch(); }, [pagination]); diff --git a/apps/cs/src/pages/template/api/template-service.ts b/apps/cs/src/pages/template/api/template-service.ts index 9a1fe4c5e..27a8dfaa3 100644 --- a/apps/cs/src/pages/template/api/template-service.ts +++ b/apps/cs/src/pages/template/api/template-service.ts @@ -42,6 +42,10 @@ export class TemplateApiService implements TemplateService { ); return response.data; } + + public async deleteTemplate(tmpltSn: string): Promise { + await client.delete(`${ENDPOINT.CMS_SERVICE.TEMPLATE}/${tmpltSn}`); + } } export const TemplateApi = new TemplateApiService(); diff --git a/apps/cs/src/pages/template/model/template-interface.ts b/apps/cs/src/pages/template/model/template-interface.ts index c6dfe7a89..1a7bc170b 100644 --- a/apps/cs/src/pages/template/model/template-interface.ts +++ b/apps/cs/src/pages/template/model/template-interface.ts @@ -32,9 +32,11 @@ export const templateFormSchema = z.object({ export type TemplateList = PageableList; export type TemplateFormData = z.infer; + export interface TemplateService { getTemplateList(params: TemplateParams): Promise; getTemplateInfo(tmpltSn: string): Promise; createTemplate(data: TemplateFormData): Promise; updateTemplate(tmpltSn: string, data: TemplateFormData): Promise; + deleteTemplate(tmpltSn: string): Promise; } diff --git a/apps/cs/src/pages/template/ui/modify-page.tsx b/apps/cs/src/pages/template/ui/modify-page.tsx index 7389cda95..987725c7a 100644 --- a/apps/cs/src/pages/template/ui/modify-page.tsx +++ b/apps/cs/src/pages/template/ui/modify-page.tsx @@ -1,6 +1,7 @@ "use client"; import { useForm } from "react-hook-form"; +import { useRouter } from "next/navigation"; import { useMutation, useQuery } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; import { ENDPOINT } from "@/shared/config"; @@ -13,6 +14,7 @@ import { TemplateApi } from "../api/template-service"; import { TemplateForm } from "./template-form"; function ModifyPage({ sequential }: { sequential: string }) { + const router = useRouter(); const { setMessage: alert } = useAlertStore((state) => state); const { data: templateDetail } = useQuery({ @@ -28,6 +30,13 @@ function ModifyPage({ sequential }: { sequential: string }) { }, }); + const { mutateAsync: deleteTemplate } = useMutation({ + mutationFn: () => TemplateApi.deleteTemplate(sequential), + onSuccess: () => { + alert("삭제되었습니다.", router.back); + }, + }); + const form = useForm({ resolver: zodResolver(templateFormSchema), values: templateDetail, @@ -42,6 +51,7 @@ function ModifyPage({ sequential }: { sequential: string }) { form={form} tmpltSn={Number(sequential)} handleSave={updateTemplate} + handleDelete={deleteTemplate} /> ); diff --git a/apps/cs/src/pages/template/ui/template-page.tsx b/apps/cs/src/pages/template/ui/template-page.tsx index b107559fe..cb6c52433 100644 --- a/apps/cs/src/pages/template/ui/template-page.tsx +++ b/apps/cs/src/pages/template/ui/template-page.tsx @@ -13,12 +13,12 @@ import { CodeSelect } from "@/entities/code"; import { ENDPOINT } from "@/shared/config"; import { TemplateApi } from "../api/template-service"; import { columns } from "../model/template-table-columns"; -import type { Template } from "../model/template-interface"; +import type { Template, TemplateParams } from "../model/template-interface"; function TemplatePage() { const router = useRouter(); - const [templateQuery, setTemplateQuery] = useState({ + const [templateQuery, setTemplateQuery] = useState({ tmpltSeCd: "", keyword: "", }); @@ -60,10 +60,6 @@ function TemplatePage() { manualPagination: true, }); - useEffect(() => { - refetch(); - }, []); - useEffect(() => { refetch(); }, [pagination]); diff --git a/apps/cs/src/pages/terms/api/terms-service.ts b/apps/cs/src/pages/terms/api/terms-service.ts new file mode 100644 index 000000000..2f06ada66 --- /dev/null +++ b/apps/cs/src/pages/terms/api/terms-service.ts @@ -0,0 +1,48 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + TermsService, + TermsList, + TermsParams, + TermsFormData, + Terms, +} from "../model/terms-interface"; + +export class TermsApiService implements TermsService { + public async getTermsList(params?: TermsParams): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.TERMS}`, + { params }, + ); + return response.data; + } + + public async getTermsInfo(trmsSn: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.TERMS}/${trmsSn}`, + ); + return response.data; + } + + public async createTerms(data: TermsFormData): Promise { + const response = await client.post( + `${ENDPOINT.CMS_SERVICE.TERMS}`, + data, + ); + return response.data; + } + + public async updateTerms(trmsSn: string, data: TermsFormData): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.TERMS}/${trmsSn}`, + data, + ); + return response.data; + } + + public async deleteTerms(trmsSn: string): Promise { + await client.delete(`${ENDPOINT.CMS_SERVICE.TERMS}/${trmsSn}`); + } +} + +export const TermsApi = new TermsApiService(); diff --git a/apps/cs/src/pages/terms/index.ts b/apps/cs/src/pages/terms/index.ts new file mode 100644 index 000000000..fc5d97a3a --- /dev/null +++ b/apps/cs/src/pages/terms/index.ts @@ -0,0 +1,3 @@ +export { CreatePage } from "./ui/create-page"; +export { ModifyPage } from "./ui/modify-page"; +export { TermsPage } from "./ui/terms-page"; diff --git a/apps/cs/src/pages/terms/model/terms-interface.ts b/apps/cs/src/pages/terms/model/terms-interface.ts new file mode 100644 index 000000000..14a0bb397 --- /dev/null +++ b/apps/cs/src/pages/terms/model/terms-interface.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import type { PageableParams, PageableList } from "@/shared/model"; + +export interface TermsParams extends PageableParams { + keywordType?: string; + keyword?: string; + trmsSeCd?: string; + useYn?: string; +} + +export interface Terms { + trmsSn: number; + trmsTtl: string; + trmsCn: string; + trmsSeCd: string; + trmsSeNm: string; + useYn: string; + delYn: string; + regDt: string; + mdfcnDt: string; + rgTrNm: string; + mdfrNm: string; +} + +export const termsFormSchema = z.object({ + trmsTtl: z.string(), + trmsCn: z.string(), + trmsSeCd: z.string(), + useYn: z.string(), +}); + +export type TermsList = PageableList; +export type TermsFormData = z.infer; + +export interface TermsService { + getTermsList(params: TermsParams): Promise; + getTermsInfo(trmsSn: string): Promise; + createTerms(data: TermsFormData): Promise; + updateTerms(trmsSn: string, data: TermsFormData): Promise; + deleteTerms(trmsSn: string): Promise; +} diff --git a/apps/cs/src/pages/terms/model/terms-table-columns.tsx b/apps/cs/src/pages/terms/model/terms-table-columns.tsx new file mode 100644 index 000000000..b70f47d42 --- /dev/null +++ b/apps/cs/src/pages/terms/model/terms-table-columns.tsx @@ -0,0 +1,35 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import type { Terms } from "./terms-interface"; +import dayjs from "dayjs"; + +export const columns: ColumnDef[] = [ + { + id: "no", + header: "NO", + cell: ({ row }) => row.index + 1, + }, + { + accessorKey: "trmsTtl", + header: "약관명", + }, + { + accessorKey: "trmsSeNm", + header: "약관구분", + }, + { + accessorKey: "useYn", + header: "사용여부", + }, + { + accessorKey: "mdfrNm", + header: "최종수정자", + }, + { + accessorKey: "mdfcnDt", + header: "최종수정일", + cell: ({ row }) => { + const date = row.getValue("mdfcnDt") as string; + return date ? dayjs(date).format("YYYY-MM-DD") : ""; + }, + }, +]; diff --git a/apps/cs/src/pages/terms/ui/create-page.tsx b/apps/cs/src/pages/terms/ui/create-page.tsx new file mode 100644 index 000000000..509fc91b2 --- /dev/null +++ b/apps/cs/src/pages/terms/ui/create-page.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { useMutation } from "@tanstack/react-query"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { TermsApi } from "../api/terms-service"; +import { termsFormSchema, type TermsFormData } from "../model/terms-interface"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { TermsForm } from "./terms-form"; + +function CreatePage() { + const router = useRouter(); + const { setMessage: alert } = useAlertStore((state) => state); + + const { mutateAsync: createTerms } = useMutation({ + mutationFn: (data: TermsFormData) => TermsApi.createTerms(data), + onSuccess: () => { + alert("등록되었습니다.", router.back); + }, + }); + + const form = useForm({ + resolver: zodResolver(termsFormSchema), + defaultValues: { + useYn: "Y", + }, + }); + + return ( + + + 약관 등록 + + + + ); +} + +export { CreatePage }; diff --git a/apps/cs/src/pages/terms/ui/modify-page.tsx b/apps/cs/src/pages/terms/ui/modify-page.tsx new file mode 100644 index 000000000..61522c2eb --- /dev/null +++ b/apps/cs/src/pages/terms/ui/modify-page.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useForm } from "react-hook-form"; +import { useRouter } from "next/navigation"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { termsFormSchema, type TermsFormData } from "../model/terms-interface"; +import { TermsApi } from "../api/terms-service"; +import { TermsForm } from "./terms-form"; + +function ModifyPage({ sequential }: { sequential: string }) { + const router = useRouter(); + const { setMessage: alert } = useAlertStore((state) => state); + + const { data: termsDetail } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.TERMS, sequential], + queryFn: () => TermsApi.getTermsInfo(sequential), + }); + + const { mutateAsync: updateTerms } = useMutation({ + mutationFn: (data: TermsFormData) => TermsApi.updateTerms(sequential, data), + onSuccess: () => { + alert("수정되었습니다."); + }, + }); + + const { mutateAsync: deleteTerms } = useMutation({ + mutationFn: () => TermsApi.deleteTerms(sequential), + onSuccess: () => { + alert("삭제되었습니다.", router.back); + }, + }); + + const form = useForm({ + resolver: zodResolver(termsFormSchema), + values: termsDetail, + }); + + return ( + + + 약관 수정 + + + + ); +} + +export { ModifyPage }; diff --git a/apps/cs/src/pages/terms/ui/terms-form.tsx b/apps/cs/src/pages/terms/ui/terms-form.tsx new file mode 100644 index 000000000..df436e63a --- /dev/null +++ b/apps/cs/src/pages/terms/ui/terms-form.tsx @@ -0,0 +1,138 @@ +import dynamic from "next/dynamic"; +import { type UseFormReturn } from "react-hook-form"; +import { + Input, + Label, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { FormWrapper } from "@/widgets/form-wrapper"; +import { CodeSelect } from "@/entities/code"; +import { FormItem, FormControl, FormField } from "@/shared/ui/form"; +import type { TermsFormData } from "../model/terms-interface"; + +const Editor = dynamic( + () => import("@/entities/editor").then((module) => module.Editor), + { ssr: false }, +); + +interface TermsFormProps { + form: UseFormReturn; + trmsSn?: number; + handleSave: (data: TermsFormData) => Promise; + handleDelete?: () => Promise; +} + +function TermsForm({ form, trmsSn, ...props }: TermsFormProps) { + return ( + + + + + + + + + + 약관 구분 + + ( + + + + + + )} + /> + + + + 약관명 + + ( + + + + + + )} + /> + + + + 약관 내용 + + ( + + + + + + )} + /> + + + + 사용 여부 + + ( + + + + + + 사용 + + + + 사용안함 + + + + + )} + /> + + + + + + + ); +} + +export { TermsForm }; diff --git a/apps/cs/src/pages/terms/ui/terms-page.tsx b/apps/cs/src/pages/terms/ui/terms-page.tsx new file mode 100644 index 000000000..5921e2fdd --- /dev/null +++ b/apps/cs/src/pages/terms/ui/terms-page.tsx @@ -0,0 +1,118 @@ +"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 { CodeSelect } from "@/entities/code"; +import { ENDPOINT } from "@/shared/config"; +import { TermsApi } from "../api/terms-service"; +import { columns } from "../model/terms-table-columns"; +import type { TermsParams, Terms } from "../model/terms-interface"; + +function TermsPage() { + const router = useRouter(); + + const [termsQuery, setTermsQuery] = useState({ + trmsSeCd: "", + keyword: "", + }); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 5, + }); + + const { data: termsList, refetch } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.TERMS], + queryFn: () => + TermsApi.getTermsList({ + ...termsQuery, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + placeholderData: keepPreviousData, + enabled: false, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); + }; + + const handleRowClick = (row: Terms) => { + router.push(`/terms/register/${row.trmsSn}`); + }; + + const table = useReactTable({ + data: termsList?.content ?? [], + columns, + rowCount: termsList?.totalElements ?? 0, + state: { + pagination, + }, + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + }); + + useEffect(() => { + refetch(); + }, [pagination]); + + return ( + + + + + + + 약관분류 + + + setTermsQuery({ ...termsQuery, trmsSeCd: value }) + } + /> + + + + 약관명 + + + setTermsQuery({ + ...termsQuery, + keyword: e.target.value, + }) + } + /> + + + + 조회 + + + + + + 약관 목록 + router.push("/terms/register")}>등록 + + + + + + + ); +} + +export { TermsPage }; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index cb140e5ea..86478ac19 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -28,6 +28,7 @@ export const ENDPOINT = { IMAGES: "/cms-service/api/v1/images", BANNERS: "/cms-service/api/v1/bnrs", TEMPLATE: "/cms-service/api/v1/tmplt", + TERMS: "/cms-service/api/v1/trms", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs",