feat: 콘텐츠 수정 작업
@@ -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 <VModifyPage sequential={sequential} />;
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
@@ -2,6 +2,7 @@ export interface ICode {
|
||||
lwpoCd: string;
|
||||
upCd: string;
|
||||
lwpoCdNm: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface CodeService {
|
||||
|
||||
@@ -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<Contents> {
|
||||
const response = await client.get<Contents>(
|
||||
`${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`,
|
||||
public async getContentsVersionList(
|
||||
params: ContentsVersionParams,
|
||||
): Promise<ContentsVersionList> {
|
||||
const response = await client.get<ContentsVersionList>(
|
||||
`${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getLatestContentsInfo(
|
||||
contsSn: number,
|
||||
contsVer: number,
|
||||
): Promise<ContentsDetail> {
|
||||
const response = await client.get<ContentsDetail>(
|
||||
`${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<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`,
|
||||
`${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}/${contsSn}/${contsVer}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { ContentsApi } from "./api/contents-service";
|
||||
export {
|
||||
CONTENTS_APPROVAL_STATUS,
|
||||
contentsFormSchema,
|
||||
type Contents,
|
||||
type ContentsParams,
|
||||
|
||||
@@ -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<typeof contentsSchema>;
|
||||
export type ContentsDetail = z.infer<typeof contentsDetailSchema>;
|
||||
export type ContentsFormData = z.infer<typeof contentsFormSchema>;
|
||||
export type ContentsList = PageableList<Contents>;
|
||||
export type ContentsVersionList = PageableList<ContentsVersion>;
|
||||
|
||||
export interface ContentsService {
|
||||
getContentsList(params: ContentsParams): Promise<ContentsList>;
|
||||
getContentsInfo(contsSn: number): Promise<Contents>;
|
||||
getContentsVersionList(
|
||||
params: ContentsVersionParams,
|
||||
): Promise<ContentsVersionList>;
|
||||
getLatestContentsInfo(
|
||||
contsSn: number,
|
||||
contsVer: number,
|
||||
): Promise<ContentsDetail>;
|
||||
createContents(data: ContentsFormData): Promise<void>;
|
||||
updateContents(contsSn: number, data: ContentsFormData): Promise<void>;
|
||||
updateContents(
|
||||
contsSn: number,
|
||||
contsVer: number,
|
||||
data: ContentsFormData,
|
||||
): Promise<void>;
|
||||
deleteContents(contsSn: number): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { ContentsPage } from "./ui/contents-page";
|
||||
export { CreatePage } from "./ui/create-page";
|
||||
export { ModifyPage } from "./ui/modify-page";
|
||||
|
||||
@@ -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<ContentsFormData>;
|
||||
contsSn?: number;
|
||||
disabledFields?: Partial<Record<keyof ContentsFormData, boolean>>;
|
||||
handleSave: (data: ContentsFormData) => Promise<void>;
|
||||
handleDelete?: () => Promise<void>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<FormWrapper form={form} isModifyMode={!!contsSn} {...props}>
|
||||
<div className="card">
|
||||
@@ -51,6 +71,7 @@ function ContentsForm({ form, contsSn, ...props }: ContentsFormProps) {
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="콘텐츠 제목을 입력하십시오."
|
||||
disabled={disabledFields?.contsTtl}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
@@ -93,13 +114,33 @@ function ContentsForm({ form, contsSn, ...props }: ContentsFormProps) {
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="useY" />
|
||||
<Label htmlFor="useY">사용</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="N" id="useN" />
|
||||
<Label htmlFor="useN">사용안함</Label>
|
||||
<RadioGroupItem value="" id="ccl-none" />
|
||||
<Label
|
||||
htmlFor="ccl-none"
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
해당없음
|
||||
</Label>
|
||||
</div>
|
||||
{cclCode?.map((code) => (
|
||||
<div
|
||||
className="flex items-center space-x-2"
|
||||
key={code.lwpoCd}
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={code.lwpoCd}
|
||||
id={`ccl-${code.lwpoCd}`}
|
||||
/>
|
||||
<Label htmlFor={`ccl-${code.lwpoCd}`}>
|
||||
<Image
|
||||
src={`/images/${code.unit}`}
|
||||
alt="CCL"
|
||||
width={77}
|
||||
height={27}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
@@ -123,13 +164,35 @@ function ContentsForm({ form, contsSn, ...props }: ContentsFormProps) {
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="useY" />
|
||||
<Label htmlFor="useY">사용</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="N" id="useN" />
|
||||
<Label htmlFor="useN">사용안함</Label>
|
||||
<RadioGroupItem value="" id="koglNone" />
|
||||
<Label
|
||||
htmlFor="koglNone"
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
해당없음
|
||||
</Label>
|
||||
</div>
|
||||
{koglCode?.map((code) => (
|
||||
<div
|
||||
className="flex items-center space-x-2"
|
||||
key={code.lwpoCd}
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={code.lwpoCd}
|
||||
id={`kogl-${code.lwpoCd}`}
|
||||
/>
|
||||
<Label htmlFor={`kogl-${code.lwpoCd}`}>
|
||||
<div className="relative h-[2.7rem] w-[11rem]">
|
||||
<Image
|
||||
src={`/images/${code.unit}`}
|
||||
alt="공공누리"
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
|
||||
@@ -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<Record<keyof ContentsFormData, boolean>>
|
||||
>({});
|
||||
|
||||
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<ContentsFormData>({
|
||||
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 (
|
||||
<div className="card card-border !mb-0 !mt-6">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">템플릿 수정</h3>
|
||||
<div>
|
||||
<Select
|
||||
value={contentsVersion.toString()}
|
||||
onValueChange={(value) => {
|
||||
setContentsVersion(Number(value));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[28rem]">
|
||||
<SelectValue placeholder="선택하세요" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{contentsVersionList?.content?.map((item) => (
|
||||
<SelectItem
|
||||
key={item.contsVer}
|
||||
value={item.contsVer.toString()}
|
||||
>
|
||||
{item.contsCn}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<ContentsForm
|
||||
form={form}
|
||||
contsSn={Number(sequential)}
|
||||
disabledFields={disabledFields}
|
||||
handleSave={isApprovedContents ? createContents : updateContents}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { ModifyPage };
|
||||
@@ -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]}`;
|
||||
};
|
||||
|
||||