Files
frontend/apps/common/features/src/widgets/post/ui/post-form.tsx
T
2026-10-06 14:32:26 +09:00

456 lines
19 KiB
TypeScript

import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import type { UseFormReturn, FieldPath } from "react-hook-form";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Input,
Label,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Textarea,
Button,
} from "@common/components";
import { BOARD_POSTING_CODE } from "@/entities/board";
import { AttachmentApi } from "@/entities/attachment";
import type { Post, PostFormData, PostUpdateFormData } from "@/entities/post";
import { ENDPOINT } from "@/shared/config";
import { useUserStore, setCookie, useMenuStore } from "@/shared/lib";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
import { PostingPeriodSelector } from "./posting-period-selector";
import { GeneralFileUploader } from "./general-file-uploader";
import { WithPreviewFileUploader } from "./with-preview-file-uploader";
type PostFormProps<T extends PostFormData | PostUpdateFormData> = {
form: UseFormReturn<T>;
handleSave: (data: T) => Promise<Post>;
};
function PostForm<T extends PostFormData | PostUpdateFormData>({
form,
handleSave,
}: PostFormProps<T>) {
const atflCd = form.getValues("atflCd" as FieldPath<T>);
const router = useRouter();
const { confirm } = useConfirm();
const bbsInfo = useMenuStore((state) => state.bbsInfo);
const userInfo = useUserStore((state) => state.userInfo);
const [deleteAttachmentIds, setDeleteAttachmentIds] = useState<string[]>([]);
const { data: attachmentData } = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, atflCd],
queryFn: () => AttachmentApi.getAttachmentsInfo(atflCd as string),
enabled: !!atflCd,
});
const onSubmit = async (data: T) => {
if (await confirm("저장하시겠습니까?")) {
if (deleteAttachmentIds.length > 0) {
await Promise.all(
deleteAttachmentIds.map(AttachmentApi.deleteAttachmentByAtflId),
);
}
await handleSave(data);
}
};
useEffect(() => {
setCookie("atfl-cd-key", "");
}, []);
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card">
<p className="mb-2 text-right text-sm text-muted-foreground">
*은 필수입력 항목입니다.
</p>
<Table variant="secondary">
<colgroup>
<col width="15%" />
<col width="85%" />
</colgroup>
<TableBody>
<TableRow>
<TableHead scope="row" data-required>제목</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name={"pstTtl" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={(field.value as string) ?? ""}
className="!text-[1.3rem]"
placeholder="제목을 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead scope="row">작성자</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name={"wrtrNm" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={(field.value as string) ?? ""}
className="w-[20rem] !text-[1.3rem]"
placeholder="작성자를 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
{!userInfo?.userUnqId && (
<TableRow>
<TableHead scope="row" data-required>비밀번호</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 as string) ?? ""}
className="w-[20rem] !text-[1.3rem]"
placeholder="비밀번호를 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
)}
<TableRow>
<TableHead scope="row">내용</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name={"pstCn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={(field.value as string) ?? ""}
onChange={field.onChange}
className="h-[20rem] w-full bg-form !text-[1.3rem] !leading-6"
placeholder="내용을 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
{bbsInfo?.rfrncUseYn === "Y" && (
<TableRow>
<TableHead scope="row">참조 URL</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name={"rfrncUrl" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={(field.value as string) ?? ""}
className="!text-[1.3rem]"
placeholder="URL을 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
)}
{bbsInfo?.popupUseYn === "Y" && (
<TableRow>
<TableHead scope="row">팝업여부</TableHead>
<TableCell className="border">
<div className="flex items-center gap-4">
<FormField
control={form.control}
name={"popupYn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="popupYn"
value={field.value?.toString() ?? ""}
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>
)}
/>
<FormField
control={form.control}
name={"popupYnEtc" as FieldPath<T>}
render={({ field }) => {
const fieldValues = form.getValues().popupYnEtc?.[0];
return (
<FormItem>
<FormControl>
<PostingPeriodSelector
defaultValues={{ ...fieldValues }}
disabled={
form.getValues(
"popupYn" as FieldPath<T>,
) === "N"
}
handlePeriodValueChange={(
periodCode,
date,
) => {
field.onChange([
{
...fieldValues,
...date,
bbsPstgTypeCd:
BOARD_POSTING_CODE.팝업게시,
pstgPrdSeCd: periodCode,
},
]);
}}
handlePostingDateChange={(date) => {
field.onChange([
{ ...fieldValues, ...date },
]);
}}
/>
</FormControl>
</FormItem>
);
}}
/>
</div>
</TableCell>
</TableRow>
)}
{bbsInfo?.upndFixUseYn === "Y" && (
<TableRow>
<TableHead scope="row">상단고정</TableHead>
<TableCell className="border">
<div className="flex items-center gap-4">
<FormField
control={form.control}
name={"upndFixYn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="upndFixYn"
value={field.value?.toString() ?? ""}
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>
)}
/>
<FormField
control={form.control}
name={"upndFixYnEtc" as FieldPath<T>}
render={({ field }) => {
const fieldValues =
form.getValues().upndFixYnEtc?.[0];
return (
<FormItem>
<FormControl>
<PostingPeriodSelector
defaultValues={{ ...fieldValues }}
disabled={
form.getValues(
"upndFixYn" as FieldPath<T>,
) === "N"
}
handlePeriodValueChange={(
periodCode,
date,
) => {
field.onChange([
{
...fieldValues,
...date,
bbsPstgTypeCd:
BOARD_POSTING_CODE.상단게시,
pstgPrdSeCd: periodCode,
},
]);
}}
handlePostingDateChange={(date) => {
field.onChange([
{ ...fieldValues, ...date },
]);
}}
/>
</FormControl>
</FormItem>
);
}}
/>
</div>
</TableCell>
</TableRow>
)}
{bbsInfo?.pstgPrdUseYn === "Y" && (
<TableRow>
<TableHead scope="row">게시기간</TableHead>
<TableCell className="border">
<div className="flex items-center gap-4">
<FormField
control={form.control}
name={"prdPstgYn" as FieldPath<T>}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="prdPstgYn"
value={field.value?.toString() ?? ""}
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>
)}
/>
<FormField
control={form.control}
name={"prdPstgYnEtc" as FieldPath<T>}
render={({ field }) => {
const fieldValues =
form.getValues().prdPstgYnEtc?.[0];
return (
<FormItem>
<FormControl>
<PostingPeriodSelector
defaultValues={{ ...fieldValues }}
disabled={
form.getValues(
"prdPstgYn" as FieldPath<T>,
) === "N"
}
handlePeriodValueChange={(
periodCode,
date,
) => {
field.onChange([
{
...fieldValues,
...date,
bbsPstgTypeCd:
BOARD_POSTING_CODE.기간게시,
pstgPrdSeCd: periodCode,
},
]);
}}
handlePostingDateChange={(date) => {
field.onChange([
{ ...fieldValues, ...date },
]);
}}
/>
</FormControl>
</FormItem>
);
}}
/>
</div>
</TableCell>
</TableRow>
)}
{bbsInfo?.atflUseYn === "Y" && (
<TableRow>
<TableHead scope="row">첨부파일</TableHead>
<TableCell className="border">
{bbsInfo?.bbsTypeCd === "BBSIMAGE" ? (
<WithPreviewFileUploader
form={form}
attachmentData={attachmentData}
/>
) : (
<GeneralFileUploader
form={form}
attachmentData={attachmentData}
deleteAttachmentIds={deleteAttachmentIds}
setDeleteAttachmentIds={setDeleteAttachmentIds}
/>
)}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button
size="lg"
color="white"
type="button"
onClick={() => router.back()}
>
취소
</Button>
<Button size="lg" type="submit">
저장
</Button>
</div>
</form>
</Form>
);
}
export { PostForm };