feat: 템플릿 관리 폼 작업

This commit is contained in:
2025-04-09 15:52:29 +09:00
parent ec4b7db2f3
commit 52343926d3
11 changed files with 243 additions and 8 deletions
-2
View File
@@ -1,2 +0,0 @@
NEXT_PUBLIC_PORT=3000
NEXT_PUBLIC_SERVER_API_URL=http://192.168.0.102:8000
+2
View File
@@ -36,3 +36,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
.env*
@@ -0,0 +1,2 @@
import { CreatePage } from "@/pages/template";
export default CreatePage;
+1
View File
@@ -18,6 +18,7 @@
"@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-query": "^5.67.1",
"@tanstack/react-table": "^8.21.2",
"@tinymce/tinymce-react": "^6.1.0",
"axios": "^1.8.1",
"chart.js": "^4.4.8",
"dayjs": "^1.11.13",
@@ -4,6 +4,7 @@ import type {
TemplateService,
TemplateList,
TemplateParams,
TemplateFormData,
} from "../model/template-interface";
export class TemplateApiService implements TemplateService {
@@ -14,6 +15,14 @@ export class TemplateApiService implements TemplateService {
);
return response.data;
}
public async createTemplate(data: TemplateFormData): Promise<void> {
const response = await client.post<void>(
`${ENDPOINT.CMS_SERVICE.TEMPLATE}`,
data,
);
return response.data;
}
}
export const TemplateApi = new TemplateApiService();
+1
View File
@@ -1 +1,2 @@
export { CreatePage } from "./ui/create-page";
export { TemplatePage } from "./ui/template-page";
@@ -1,3 +1,4 @@
import { z } from "zod";
import type { PageableParams, PageableList } from "@/shared/model";
export interface TemplateParams extends PageableParams {
@@ -18,8 +19,16 @@ export interface Template {
mdfrNm: string;
}
export type TemplateList = PageableList<Template>;
export const templateFormSchema = z.object({
tmpltNm: z.string(),
tmpltCn: z.string(),
tmpltSeCd: z.string(),
useYn: z.string(),
});
export type TemplateList = PageableList<Template>;
export type TemplateFormData = z.infer<typeof templateFormSchema>;
export interface TemplateService {
getTemplateList(params: TemplateParams): Promise<TemplateList>;
createTemplate(data: TemplateFormData): Promise<void>;
}
@@ -0,0 +1,42 @@
"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 { TemplateApi } from "../api/template-service";
import {
templateFormSchema,
type TemplateFormData,
} from "../model/template-interface";
import { useAlertStore } from "@/shared/lib/use-alert-store";
import { TemplateForm } from "./template-form";
function CreatePage() {
const router = useRouter();
const { setMessage: alert } = useAlertStore((state) => state);
const { mutateAsync: createTemplate } = useMutation({
mutationFn: (data: TemplateFormData) => TemplateApi.createTemplate(data),
onSuccess: () => {
alert("등록되었습니다.", router.back);
},
});
const form = useForm<TemplateFormData>({
resolver: zodResolver(templateFormSchema),
defaultValues: {
useYn: "Y",
},
});
return (
<div className="card card-border !mb-0 !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">템플릿 등록</h3>
</div>
<TemplateForm form={form} handleSave={createTemplate} />
</div>
);
}
export { CreatePage };
@@ -0,0 +1,134 @@
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 { TemplateFormData } from "../model/template-interface";
const Editor = dynamic(
() => import("@/shared/ui/editor").then((module) => module.Editor),
{ ssr: false },
);
interface TemplateFormProps {
form: UseFormReturn<TemplateFormData>;
tmpltSn?: number;
handleSave: (data: TemplateFormData) => Promise<void>;
handleDelete?: () => Promise<void>;
}
function TemplateForm({ form, tmpltSn, ...props }: TemplateFormProps) {
return (
<FormWrapper form={form} isModifyMode={!!tmpltSn} {...props}>
<div className="card">
<Table variant="secondary">
<colgroup>
<col width="20%" />
<col width="80%" />
</colgroup>
<TableBody>
<TableRow>
<TableHead>템플릿 구분</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="tmpltSeCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
upCd="TMPLTCLCD"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>템플릿명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="tmpltNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="템플릿명을 입력하십시오."
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>템플릿 내용</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="tmpltCn"
render={() => (
<FormItem>
<FormControl>
<Editor />
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사용 여부</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="useYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="useYn"
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>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</FormWrapper>
);
}
export { TemplateForm };
+19
View File
@@ -0,0 +1,19 @@
import { Editor as TinyEditor } from "@tinymce/tinymce-react";
const Editor = () => {
return (
<TinyEditor
apiKey={process.env.NEXT_PUBLIC_TINY_API_KEY}
init={{
language: "ko_KR",
height: 500,
menubar: false,
plugins: "lists advlist table",
toolbar:
"undo redo | blocks fontfamily fontsize | bold italic underline strikethrough forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist blockquote | table",
}}
/>
);
};
export { Editor };
+23 -5
View File
@@ -66,6 +66,9 @@ importers:
'@tanstack/react-table':
specifier: ^8.21.2
version: 8.21.2(react-dom@18.3.1)(react@18.3.1)
'@tinymce/tinymce-react':
specifier: ^6.1.0
version: 6.1.0(react-dom@18.3.1)(react@18.3.1)
axios:
specifier: ^1.8.1
version: 1.8.1
@@ -1632,7 +1635,7 @@ packages:
magic-string: 0.27.0
react-docgen-typescript: 2.2.2(typescript@5.6.3)
typescript: 5.6.3
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
dev: true
/@jridgewell/gen-mapping@0.3.8:
@@ -3097,7 +3100,7 @@ packages:
browser-assert: 1.2.1
storybook: 8.6.0(prettier@3.5.2)
ts-dedent: 2.2.0
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
dev: true
/@storybook/components@8.6.0(storybook@8.6.0):
@@ -3229,7 +3232,7 @@ packages:
resolve: 1.22.10
storybook: 8.6.0(prettier@3.5.2)
tsconfig-paths: 4.2.0
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
transitivePeerDependencies:
- rollup
- supports-color
@@ -3363,6 +3366,21 @@ packages:
'@testing-library/dom': 10.4.0
dev: true
/@tinymce/tinymce-react@6.1.0(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-K0MP3yYVKe8+etUwsg6zyRq+q9TGLaVf005WiBHiB8JZEomAwbBPERGunhU9uOqNQ5gJs8yVOPZ68Xcd1UHclA==}
peerDependencies:
react: ^18.3.1
react-dom: ^18.3.1
tinymce: ^7.0.0 || ^6.0.0 || ^5.5.1
peerDependenciesMeta:
tinymce:
optional: true
dependencies:
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@types/aria-query@5.0.4:
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
dev: true
@@ -3675,7 +3693,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
transitivePeerDependencies:
- supports-color
dev: true
@@ -8474,7 +8492,7 @@ packages:
debug: 4.4.0
globrex: 0.1.2
tsconfck: 3.1.5(typescript@5.6.3)
vite: 6.2.0(@types/node@22.13.5)
vite: 6.2.0(@types/node@22.13.9)
transitivePeerDependencies:
- supports-color
- typescript