diff --git a/apps/cs/app/(with-base-layout)/contents/register/[sequential]/page.tsx b/apps/cs/app/(with-base-layout)/contents/register/[sequential]/page.tsx new file mode 100644 index 000000000..aa9320b8f --- /dev/null +++ b/apps/cs/app/(with-base-layout)/contents/register/[sequential]/page.tsx @@ -0,0 +1,10 @@ +import { ModifyPage as VModifyPage } from "@/pages/contents"; + +export default async function ModifyPage({ + params, +}: { + params: Promise<{ sequential: string }>; +}) { + const { sequential } = await params; + return ; +} diff --git a/apps/cs/public/images/img_cc_by.png b/apps/cs/public/images/img_cc_by.png new file mode 100755 index 000000000..628cc2bfd Binary files /dev/null and b/apps/cs/public/images/img_cc_by.png differ diff --git a/apps/cs/public/images/img_cc_by_nc.png b/apps/cs/public/images/img_cc_by_nc.png new file mode 100755 index 000000000..86f60c5db Binary files /dev/null and b/apps/cs/public/images/img_cc_by_nc.png differ diff --git a/apps/cs/public/images/img_cc_by_nc_nd.png b/apps/cs/public/images/img_cc_by_nc_nd.png new file mode 100755 index 000000000..9d8ccb18d Binary files /dev/null and b/apps/cs/public/images/img_cc_by_nc_nd.png differ diff --git a/apps/cs/public/images/img_cc_by_nc_sa.png b/apps/cs/public/images/img_cc_by_nc_sa.png new file mode 100755 index 000000000..00f402c9e Binary files /dev/null and b/apps/cs/public/images/img_cc_by_nc_sa.png differ diff --git a/apps/cs/public/images/img_cc_by_nd.png b/apps/cs/public/images/img_cc_by_nd.png new file mode 100755 index 000000000..0da64acd8 Binary files /dev/null and b/apps/cs/public/images/img_cc_by_nd.png differ diff --git a/apps/cs/public/images/img_cc_by_sa.png b/apps/cs/public/images/img_cc_by_sa.png new file mode 100755 index 000000000..6d94e60b9 Binary files /dev/null and b/apps/cs/public/images/img_cc_by_sa.png differ diff --git a/apps/cs/public/images/img_opentype01.png b/apps/cs/public/images/img_opentype01.png new file mode 100755 index 000000000..456c7c6bb Binary files /dev/null and b/apps/cs/public/images/img_opentype01.png differ diff --git a/apps/cs/public/images/img_opentype02.png b/apps/cs/public/images/img_opentype02.png new file mode 100755 index 000000000..a6c5bfb32 Binary files /dev/null and b/apps/cs/public/images/img_opentype02.png differ diff --git a/apps/cs/public/images/img_opentype03.png b/apps/cs/public/images/img_opentype03.png new file mode 100755 index 000000000..970a6a0d4 Binary files /dev/null and b/apps/cs/public/images/img_opentype03.png differ diff --git a/apps/cs/public/images/img_opentype04.png b/apps/cs/public/images/img_opentype04.png new file mode 100755 index 000000000..41d284cab Binary files /dev/null and b/apps/cs/public/images/img_opentype04.png differ diff --git a/apps/cs/src/entities/code/model/code-interface.ts b/apps/cs/src/entities/code/model/code-interface.ts index 0d066e6da..4562e2559 100644 --- a/apps/cs/src/entities/code/model/code-interface.ts +++ b/apps/cs/src/entities/code/model/code-interface.ts @@ -2,6 +2,7 @@ export interface ICode { lwpoCd: string; upCd: string; lwpoCdNm: string; + unit: string; } export interface CodeService { diff --git a/apps/cs/src/entities/content/api/contents-service.ts b/apps/cs/src/entities/content/api/contents-service.ts index 06f141fae..eedc25c54 100644 --- a/apps/cs/src/entities/content/api/contents-service.ts +++ b/apps/cs/src/entities/content/api/contents-service.ts @@ -4,8 +4,10 @@ import type { ContentsService, ContentsParams, ContentsList, - Contents, ContentsFormData, + ContentsVersionParams, + ContentsVersionList, + ContentsDetail, } from "../model/contents-interface"; export class ContentsApiService implements ContentsService { @@ -17,9 +19,22 @@ export class ContentsApiService implements ContentsService { return response.data; } - public async getContentsInfo(contsSn: number): Promise { - const response = await client.get( - `${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`, + public async getContentsVersionList( + params: ContentsVersionParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}`, + { params }, + ); + return response.data; + } + + public async getLatestContentsInfo( + contsSn: number, + contsVer: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}/${contsSn}/${contsVer}`, ); return response.data; } @@ -34,10 +49,11 @@ export class ContentsApiService implements ContentsService { public async updateContents( contsSn: number, + contsVer: number, data: ContentsFormData, ): Promise { const response = await client.put( - `${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`, + `${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}/${contsSn}/${contsVer}`, data, ); return response.data; diff --git a/apps/cs/src/entities/content/index.ts b/apps/cs/src/entities/content/index.ts index 5df94d667..6bf6ebd45 100644 --- a/apps/cs/src/entities/content/index.ts +++ b/apps/cs/src/entities/content/index.ts @@ -1,5 +1,6 @@ export { ContentsApi } from "./api/contents-service"; export { + CONTENTS_APPROVAL_STATUS, contentsFormSchema, type Contents, type ContentsParams, diff --git a/apps/cs/src/entities/content/model/contents-interface.ts b/apps/cs/src/entities/content/model/contents-interface.ts index 7a7c6dcf0..773cdc161 100644 --- a/apps/cs/src/entities/content/model/contents-interface.ts +++ b/apps/cs/src/entities/content/model/contents-interface.ts @@ -1,12 +1,38 @@ import { z } from "zod"; import type { PageableList, PageableParams } from "@/shared/model"; +export const CONTENTS_APPROVAL_STATUS = { + DRAFT: "01", + REQUESTED: "02", + APPROVED: "99", +} as const; + +export type ContentsApprovalStatus = + (typeof CONTENTS_APPROVAL_STATUS)[keyof typeof CONTENTS_APPROVAL_STATUS]; + export interface ContentsParams extends PageableParams { contsTtl?: string; aprvContsVer?: number; useYn?: string; } +export interface ContentsVersionParams extends PageableParams { + contsSn?: number; + contsCn?: string; + aprvCd?: string; + cclCd?: string; + koglCd?: string; + tag?: string; + useYn?: string; +} + +export interface ContentsVersion { + contsSn: number; + contsVer: number; + contsCn: string; + useYn: string; +} + export const contentsSchema = z.object({ contsSn: z.number(), contsTtl: z.string(), @@ -22,26 +48,47 @@ export const contentsSchema = z.object({ workContsVer: z.number(), }); -export const contentsFormSchema = z.object({ - contsSn: z.number().nullish(), +export const contentsDetailSchema = z.object({ + contsSn: z.number(), + contsVer: z.number(), contsTtl: z.string(), contsCn: z.string(), contsRmrk: z.string().nullish(), - aprvCd: z.string().nullish(), + aprvCd: z.string(), cclCd: z.string().nullish(), koglCd: z.string().nullish(), tag: z.string().nullish(), + aprvDt: z.string(), + autzr: z.string(), useYn: z.string(), }); +export const contentsFormSchema = contentsDetailSchema.omit({ + contsVer: true, + aprvDt: true, + autzr: true, +}); + export type Contents = z.infer; +export type ContentsDetail = z.infer; export type ContentsFormData = z.infer; export type ContentsList = PageableList; +export type ContentsVersionList = PageableList; export interface ContentsService { getContentsList(params: ContentsParams): Promise; - getContentsInfo(contsSn: number): Promise; + getContentsVersionList( + params: ContentsVersionParams, + ): Promise; + getLatestContentsInfo( + contsSn: number, + contsVer: number, + ): Promise; createContents(data: ContentsFormData): Promise; - updateContents(contsSn: number, data: ContentsFormData): Promise; + updateContents( + contsSn: number, + contsVer: number, + data: ContentsFormData, + ): Promise; deleteContents(contsSn: number): Promise; } diff --git a/apps/cs/src/pages/contents/index.ts b/apps/cs/src/pages/contents/index.ts index f6dc4a528..2bb0d4fb2 100644 --- a/apps/cs/src/pages/contents/index.ts +++ b/apps/cs/src/pages/contents/index.ts @@ -1,2 +1,3 @@ export { ContentsPage } from "./ui/contents-page"; export { CreatePage } from "./ui/create-page"; +export { ModifyPage } from "./ui/modify-page"; diff --git a/apps/cs/src/pages/contents/ui/contents-form.tsx b/apps/cs/src/pages/contents/ui/contents-form.tsx index 0bbd122de..d61084646 100644 --- a/apps/cs/src/pages/contents/ui/contents-form.tsx +++ b/apps/cs/src/pages/contents/ui/contents-form.tsx @@ -14,6 +14,10 @@ import { import { FormWrapper } from "@/widgets/form-wrapper"; import { FormItem, FormControl, FormField } from "@/shared/ui/form"; import type { ContentsFormData } from "@/entities/content"; +import { useQuery } from "@tanstack/react-query"; +import { ENDPOINT } from "@/shared/config"; +import { CodeApi } from "@/entities/code/api/code-service"; +import Image from "next/image"; const Editor = dynamic( () => import("@/entities/editor").then((module) => module.Editor), @@ -23,11 +27,27 @@ const Editor = dynamic( interface ContentsFormProps { form: UseFormReturn; contsSn?: number; + disabledFields?: Partial>; handleSave: (data: ContentsFormData) => Promise; handleDelete?: () => Promise; } -function ContentsForm({ form, contsSn, ...props }: ContentsFormProps) { +function ContentsForm({ + form, + contsSn, + disabledFields, + ...props +}: ContentsFormProps) { + const { data: cclCode } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CODE, "CCLCD"], + queryFn: () => CodeApi.getCode("CCLCD"), + }); + + const { data: koglCode } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CODE, "KOGLCD"], + queryFn: () => CodeApi.getCode("KOGLCD"), + }); + return (
@@ -51,6 +71,7 @@ function ContentsForm({ form, contsSn, ...props }: ContentsFormProps) { value={field.value ?? ""} className="!text-[1.3rem]" placeholder="콘텐츠 제목을 입력하십시오." + disabled={disabledFields?.contsTtl} /> @@ -93,13 +114,33 @@ function ContentsForm({ form, contsSn, ...props }: ContentsFormProps) { onValueChange={field.onChange} >
- - -
-
- - + +
+ {cclCode?.map((code) => ( +
+ + +
+ ))} @@ -123,13 +164,35 @@ function ContentsForm({ form, contsSn, ...props }: ContentsFormProps) { onValueChange={field.onChange} >
- - -
-
- - + +
+ {koglCode?.map((code) => ( +
+ + +
+ ))} diff --git a/apps/cs/src/pages/contents/ui/modify-page.tsx b/apps/cs/src/pages/contents/ui/modify-page.tsx new file mode 100644 index 000000000..52241a5cc --- /dev/null +++ b/apps/cs/src/pages/contents/ui/modify-page.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +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 { + CONTENTS_APPROVAL_STATUS, + ContentsApi, + contentsFormSchema, + type ContentsFormData, +} from "@/entities/content"; +import { ContentsForm } from "./contents-form"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@common/components/ui"; + +function ModifyPage({ sequential }: { sequential: string }) { + const { setMessage: alert } = useAlertStore((state) => state); + + const [contentsVersion, setContentsVersion] = useState(0); + const [disabledFields, setDisabledFields] = useState< + Partial> + >({}); + + const { data: contentsVersionList } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, sequential], + queryFn: () => + ContentsApi.getContentsVersionList({ + contsSn: Number(sequential), + page: 0, + size: 100, + }), + }); + + const { data: contentsDetail } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contentsVersion], + queryFn: () => + ContentsApi.getLatestContentsInfo(Number(sequential), contentsVersion), + enabled: !!contentsVersion, + }); + + const { mutateAsync: createContents } = useMutation({ + mutationFn: (data: ContentsFormData) => ContentsApi.createContents(data), + onSuccess: () => { + alert("수정되었습니다."); + }, + }); + + const { mutateAsync: updateContents } = useMutation({ + mutationFn: (data: ContentsFormData) => + ContentsApi.updateContents( + Number(sequential), + contentsDetail?.contsVer ?? 0, + data, + ), + onSuccess: () => { + alert("수정되었습니다."); + }, + }); + + const form = useForm({ + resolver: zodResolver(contentsFormSchema), + values: contentsDetail, + }); + + const isApprovedContents = + contentsDetail?.aprvCd === CONTENTS_APPROVAL_STATUS.APPROVED; + + useEffect(() => { + if (!contentsVersionList?.content?.length) return; + + const latestContent = contentsVersionList?.content[0]; + if (!latestContent) return; + + if (latestContent?.contsVer > 1) { + setDisabledFields({ + contsTtl: true, + }); + } + setContentsVersion(latestContent?.contsVer ?? 0); + }, [contentsVersionList?.content]); + + return ( +
+
+

템플릿 수정

+
+ +
+
+ +
+ ); +} + +export { ModifyPage }; diff --git a/apps/cs/src/pages/file/model/table-columns.tsx b/apps/cs/src/pages/file/model/table-columns.tsx index 0bc7e5c23..2606397c9 100644 --- a/apps/cs/src/pages/file/model/table-columns.tsx +++ b/apps/cs/src/pages/file/model/table-columns.tsx @@ -28,6 +28,10 @@ export const createColumns = ( { accessorKey: "atflFlieSz", header: "파일크기", + cell: ({ row }) => { + const size = row.getValue("atflFlieSz"); + return size ? formatFileSize(Number(size)) : "0 Bytes"; + }, }, { @@ -60,3 +64,14 @@ export const createColumns = ( ), }, ]; + +const formatFileSize = (bytes: number): string => { + console.log("bytes: ", bytes); + if (bytes === 0) return "0 Bytes"; + + const k = 1024; + const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; +};