WIP: 게시글 등록, 수정 작업중

This commit is contained in:
2025-04-30 18:44:00 +09:00
parent 5497a136c9
commit 0b0d567072
12 changed files with 442 additions and 11 deletions
@@ -0,0 +1,10 @@
import { ModifyPage } from "@/pages/notice";
export default async function PstBbsNoticePostModifyPage({
params,
}: {
params: Promise<{ bbsId: string; pstNo: string }>;
}) {
const { bbsId, pstNo } = await params;
return <ModifyPage bbsId={bbsId} pstNo={pstNo} />;
}
@@ -1,10 +1,13 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
Post,
PostInfo,
PostList,
PostParams,
PostService,
PostFormData,
PostUpdateFormData,
} from "../model/post-interface";
export class PostApiService implements PostService {
@@ -22,6 +25,26 @@ export class PostApiService implements PostService {
return response.data;
}
public async createPost(data: PostFormData): Promise<Post> {
const response = await client.post<Post>(
`${ENDPOINT.BOARD_SERVICE.POSTS}`,
data,
);
return response.data;
}
public async updatePost(
bbsId: string,
pstNo: string,
data: PostUpdateFormData,
): Promise<Post> {
const response = await client.put<Post>(
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
data,
);
return response.data;
}
public async deletePost(bbsId: string, pstNo: string): Promise<void> {
await client.delete(`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`);
}
@@ -1,7 +1,11 @@
export { PostApi } from "./api/post-service";
export type {
Post,
PostList,
PostParams,
PostService,
export {
postFormSchema,
postUpdateFormSchema,
type Post,
type PostList,
type PostParams,
type PostService,
type PostFormData,
type PostUpdateFormData,
} from "./model/post-interface";
@@ -1,3 +1,4 @@
import { z } from "zod";
import {
type PageableList,
type PageableParams,
@@ -38,10 +39,39 @@ export interface PostInfo extends Post {
mdfr: string;
}
export const postFormSchema = z.object({
bbsId: z.number(),
pstTtl: z.string(),
pstCn: z.string().nullish(),
rfrncUrl: z.string().nullish(),
popupYn: z.string().nullish(),
upndFixYn: z.string().nullish(),
prdPstgYn: z.string().nullish(),
pstPswd: z.string(),
wrtrNm: z.string().nullish(),
wrtrCplc: z.string().nullish(),
atflCd: z.string().nullish(),
parentPstNo: z.number().nullish(),
});
export const postUpdateFormSchema = postFormSchema.omit({
bbsId: true,
pstPswd: true,
parentPstNo: true,
});
export type PostList = PageableList<Post>;
export type PostFormData = z.infer<typeof postFormSchema>;
export type PostUpdateFormData = z.infer<typeof postUpdateFormSchema>;
export interface PostService {
getPostList(params: PostParams): Promise<PostList>;
getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo>;
createPost(data: PostFormData): Promise<Post>;
updatePost(
bbsId: string,
pstNo: string,
data: PostUpdateFormData,
): Promise<Post>;
deletePost(bbsId: string, pstNo: string): Promise<void>;
}
@@ -1,2 +1,3 @@
export { NoticePage } from "./ui/notice-page";
export { NoticePost } from "./ui/notice-post";
export { ModifyPage } from "./ui/modify-page";
@@ -0,0 +1,45 @@
"use client";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
import { useAlertStore } from "@/shared/lib/use-alert-store";
import { postFormSchema, type PostFormData } from "@/entities/post";
import { PostApi } from "@/entities/post";
import { NoticeForm } from "./notice-form";
import { NoticeLayout } from "./notice-layout";
function CreatePage() {
const router = useRouter();
const { setMessage: alert } = useAlertStore((state) => state);
const { mutateAsync: createPost } = useMutation({
mutationFn: (data: PostFormData) => PostApi.createPost(data),
onSuccess: () => {
alert("등록되었습니다.", router.back);
},
});
const form = useForm<PostFormData>({
resolver: zodResolver(postFormSchema),
defaultValues: {
bbsId: 0,
pstTtl: "",
pstPswd: "",
},
});
return (
<NoticeLayout>
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">게시글 등록</h3>
</div>
<NoticeForm form={form} handleSave={createPost} />
</div>
</NoticeLayout>
);
}
export { CreatePage };
@@ -0,0 +1,49 @@
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery } from "@tanstack/react-query";
import {
PostApi,
postUpdateFormSchema,
type PostUpdateFormData,
} from "@/entities/post";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore } from "@/shared/lib/use-alert-store";
import { NoticeForm } from "./notice-form";
import { NoticeLayout } from "./notice-layout";
function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { setMessage: alert } = useAlertStore((state) => state);
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
onSuccess: () => {
alert("수정되었습니다.");
},
});
const form = useForm<PostUpdateFormData>({
resolver: zodResolver(postUpdateFormSchema),
values: postInfo,
});
return (
<NoticeLayout>
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">게시글 수정</h3>
</div>
<NoticeForm form={form} handleSave={updatePost} />
</div>
</NoticeLayout>
);
}
export { ModifyPage };
@@ -0,0 +1,262 @@
import type { UseFormReturn, FieldPath } from "react-hook-form";
import type { Post, PostFormData, PostUpdateFormData } from "@/entities/post";
import {
Input,
Label,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Textarea,
Button,
} from "@common/components";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
type NoticeFormProps<T extends PostFormData | PostUpdateFormData> = {
form: UseFormReturn<T>;
handleSave: (data: T) => Promise<Post>;
};
function NoticeForm<T extends PostFormData | PostUpdateFormData>({
form,
handleSave,
}: NoticeFormProps<T>) {
const onSubmit = async (data: T) => {
await handleSave(data);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card">
<Table variant="secondary">
<colgroup>
<col width="15%" />
<col width="35%" />
<col width="15%" />
<col width="35%" />
</colgroup>
<TableBody>
<TableRow>
<TableHead>제목</TableHead>
<TableCell className="border" colSpan={3}>
<FormField
control={form.control}
name={"pstTtl" as FieldPath<T>}
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={"wrtrNm" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="작성자를 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
<TableHead>비밀번호</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name={"pstPswd" as FieldPath<T>}
render={({ field }) => (
<FormItem className="flex flex-col">
<FormControl>
<Input
{...field}
type="password"
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="비밀번호를 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>내용</TableHead>
<TableCell className="border" colSpan={3}>
<FormField
control={form.control}
name={"pstCn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={field.value ?? ""}
onChange={field.onChange}
className="w-full !text-[1.3rem]"
placeholder="내용을 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>참조 URL</TableHead>
<TableCell className="border" colSpan={3}>
<FormField
control={form.control}
name={"rfrncUrl" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="URL을 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>팝업여부</TableHead>
<TableCell className="border" colSpan={3}>
<FormField
control={form.control}
name={"popupYn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="popupYn"
value={String(field.value) ?? ""}
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="popupY" />
<Label htmlFor="popupY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="popupN" />
<Label htmlFor="popupN">사용안함</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>상단고정</TableHead>
<TableCell className="border" colSpan={3}>
<FormField
control={form.control}
name={"upndFixYn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="upndFixYn"
value={String(field.value) ?? ""}
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="upndFixY" />
<Label htmlFor="upndFixY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="upndFixN" />
<Label htmlFor="upndFixN">사용안함</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>게시기간</TableHead>
<TableCell className="border" colSpan={3}>
<FormField
control={form.control}
name={"prdPstgYn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="prdPstgYn"
value={String(field.value) ?? ""}
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="prdPstgY" />
<Label htmlFor="prdPstgY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="prdPstgN" />
<Label htmlFor="prdPstgN">사용안함</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>첨부파일</TableHead>
<TableCell className="border" colSpan={3}>
<FormField
control={form.control}
name={"atflCd" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl></FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
<div className="mt-4 flex justify-center">
<Button type="submit" size="lg">
저장
</Button>
</div>
</div>
</form>
</Form>
);
}
export { NoticeForm };
@@ -1,4 +1,4 @@
import { useRouter } from "next/navigation";
import { useRouter, usePathname } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components";
@@ -12,6 +12,8 @@ type PostActionProps = {
function PostAction({ children, bbsId, pstNo }: PostActionProps) {
const router = useRouter();
const pathname = usePathname();
const { confirm } = useConfirm();
const { mutate: deletePost } = useMutation({
@@ -37,7 +39,11 @@ function PostAction({ children, bbsId, pstNo }: PostActionProps) {
>
목록
</Button>
<Button size="lg" type="submit">
<Button
size="lg"
type="button"
onClick={() => router.push(`${pathname}/modify`)}
>
수정
</Button>
<Button size="lg" color="red" type="button" onClick={onDelete}>
+2 -2
View File
@@ -5,8 +5,8 @@ import presetConfig from "../../../tailwind.config.preset";
export default {
presets: [presetConfig],
content: [
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"../../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{js,ts,jsx,tsx}",
"../../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
+1
View File
@@ -1,5 +1,6 @@
@import "@common/assets/styles/base.css";
@import "@common/assets/styles/diveicon.css";
@import "@common/components/index.css";
@tailwind base;
@tailwind utilities;
+2 -2
View File
@@ -5,8 +5,8 @@ import presetConfig from "../../tailwind.config.preset";
export default {
presets: [presetConfig],
content: [
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{js,ts,jsx,tsx}",
"../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {