WIP: 게시글 등록, 수정 작업중
This commit is contained in:
+10
@@ -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}>
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
@import "@common/assets/styles/base.css";
|
||||
@import "@common/assets/styles/diveicon.css";
|
||||
@import "@common/components/index.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user