refactor: form wrapper 선언 및 적용

This commit is contained in:
2025-04-08 13:59:04 +09:00
parent d6b48ea554
commit 77539bb785
13 changed files with 1146 additions and 1259 deletions
+1
View File
@@ -0,0 +1 @@
export { CodeSelect } from "../ui/code-select";
@@ -25,8 +25,8 @@ import {
DialogFooter,
} from "@common/components/ui";
import { cn } from "@common/components/lib";
import { CodeSelect } from "@/entities/code";
import { SiteApi, type Site } from "@/entities/site";
import { CodeSelect } from "@/entities/code/@x/posting";
import { SiteApi, type Site } from "@/entities/site/@x/posting";
import type { PageableList } from "@/shared/model";
import { ENDPOINT } from "@/shared/config";
import { PostingApi } from "../api/posting-service";
+2
View File
@@ -0,0 +1,2 @@
export { SiteApi } from "../api/site-service";
export type { Site } from "../model/site-interface";
@@ -35,17 +35,17 @@ export interface SiteDetail extends SiteFormData {
export const siteFormSchema = z.object({
siteId: z.string().max(2).nonempty(),
siteNm: z.string().max(100).nonempty(),
siteExpln: z.string().max(1000).nullable(),
siteExpln: z.string().max(1000).nullish(),
siteKndCd: z.string().max(10).nonempty(),
siteAddr: z.string().max(2000).nullable(),
faxNo: z.string().max(100).nullable(),
telNo: z.string().max(1000).nullable(),
lwndCn: z.string().max(2000).nullable(),
siteAddr: z.string().max(2000).nullish(),
faxNo: z.string().max(100).nullish(),
telNo: z.string().max(1000).nullish(),
lwndCn: z.string().max(2000).nullish(),
bscSiteYn: z.string(),
bkmkIcon: z.string().max(100).nullable(),
bkmkIcon: z.string().max(100).nullish(),
useYn: z.string(),
siteSkn: z.string().max(10).nullable(),
dmnAddr: z.string().nullable(),
siteSkn: z.string().max(10).nullish(),
dmnAddr: z.string().nullish(),
});
export type SiteFormData = z.infer<typeof siteFormSchema>;
+217 -265
View File
@@ -2,13 +2,10 @@
import Image from "next/image";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import type { UseFormReturn } from "react-hook-form";
import type { HttpResponse, Upload } from "tus-js-client";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Button,
FileUploader,
Input,
Label,
@@ -25,9 +22,10 @@ import {
import { CodeSelect } from "@/entities/code";
import { AttachmentApi } from "@/entities/attachment";
import { ENDPOINT } from "@/shared/config";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
import { FormControl, FormField, FormItem } from "@/shared/ui/form";
import type { BannerFormData } from "../model/banner-interface";
import { handleUpload } from "../lib/utils";
import { FormWrapper } from "@/widgets/form-wrapper";
type BannerFormProps = {
form: UseFormReturn<BannerFormData>;
@@ -36,17 +34,9 @@ type BannerFormProps = {
handleDelete?: () => Promise<void>;
};
function BannerForm({
form,
bnrSn,
handleSave,
handleDelete,
}: BannerFormProps) {
const isModifyMode = !!bnrSn;
function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
const atflCd = form.watch("atflCd");
const router = useRouter();
const { confirm } = useConfirm();
const [imageUrl, setImageUrl] = useState("");
const [upload, setUpload] = useState<Upload | null>(null);
const [progress, setProgress] = useState(0);
@@ -58,22 +48,6 @@ function BannerForm({
enabled: !!atflCd,
});
const onSubmit = async (data: BannerFormData) => {
const confirmMessage = isModifyMode
? "수정하시겠습니까?"
: "저장하시겠습니까?";
if (await confirm(confirmMessage)) {
await handleSave(data);
}
};
const onDelete = async () => {
if (await confirm("삭제하시겠습니까?")) {
await handleDelete?.();
}
};
const handleProgress = (percentage: string) => {
setProgress(Number(percentage));
};
@@ -96,245 +70,223 @@ function BannerForm({
}, [atflId]);
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<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="bnrTtl"
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="bnrTypeCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
value={field.value ?? ""}
upCd="BNRTYPE"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>배너 이미지</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="atflCd"
render={() => (
<FormItem className="flex flex-col">
<FormControl>
<FileUploader
id="atflCd"
className="!h-auto !max-w-none flex-col !items-start"
accept=".jpeg,.jpg,.png"
onUpload={(e) => {
const files = e.target.files;
if (!files) return;
handleUpload(files, {
handleUploadStart: setUpload,
handleProgress,
handleSuccess: setUpload,
handleResponse: handleUploadResponse,
});
}}
customPreview={
<>
<Label htmlFor="atflCd">
<div className="relative h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
<p className="absolute bottom-3 left-0 p-2 text-base leading-none">
{(upload?.file as File)?.name}
</p>
<div className="relative h-full w-full">
{imageUrl && (
<Image
src={imageUrl}
alt="업로드된 이미지"
fill
objectFit="contain"
/>
)}
</div>
<Progress
value={progress}
className="absolute bottom-0 left-0"
<FormWrapper form={form} isModifyMode={!!bnrSn} {...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="bnrTtl"
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="bnrTypeCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
value={field.value ?? ""}
upCd="BNRTYPE"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>배너 이미지</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="atflCd"
render={() => (
<FormItem className="flex flex-col">
<FormControl>
<FileUploader
id="atflCd"
className="!h-auto !max-w-none flex-col !items-start"
accept=".jpeg,.jpg,.png"
onUpload={(e) => {
const files = e.target.files;
if (!files) return;
handleUpload(files, {
handleUploadStart: setUpload,
handleProgress,
handleSuccess: setUpload,
handleResponse: handleUploadResponse,
});
}}
customPreview={
<>
<Label htmlFor="atflCd">
<div className="relative h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
<p className="absolute bottom-3 left-0 p-2 text-base leading-none">
{(upload?.file as File)?.name}
</p>
<div className="relative h-full w-full">
{imageUrl && (
<Image
src={imageUrl}
alt="업로드된 이미지"
fill
objectFit="contain"
/>
</div>
</Label>
<div className="mb-2 text-lg font-medium text-label">
<ul className="ml-10 list-disc">
<li>
이미지 파일(jpg, jpeg, png, gif)만
업로드 가능합니다.
</li>
</ul>
)}
</div>
</>
}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>링크 URL</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lnkgUrlAddr"
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="newWndwYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="newWndwYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="newWndwN" />
<Label htmlFor="newWndwN">현재창</Label>
<Progress
value={progress}
className="absolute bottom-0 left-0"
/>
</div>
</Label>
<div className="mb-2 text-lg font-medium text-label">
<ul className="ml-10 list-disc">
<li>
이미지 파일(jpg, jpeg, png, gif)만 업로드
가능합니다.
</li>
</ul>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="newWndwY" />
<Label htmlFor="newWndwY">새창</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>배너 설명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bnrExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={field.value ?? ""}
className="!text-[1.3rem] !leading-6"
placeholder="배너에 대한 설명을 입력해주세요"
/>
</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>
<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>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
)}
</div>
</form>
</Form>
</>
</>
}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>링크 URL</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lnkgUrlAddr"
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="newWndwYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="newWndwYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="newWndwN" />
<Label htmlFor="newWndwN">현재창</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="newWndwY" />
<Label htmlFor="newWndwY">새창</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>배너 설명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bnrExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={field.value ?? ""}
className="!text-[1.3rem] !leading-6"
placeholder="배너에 대한 설명을 입력해주세요"
/>
</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>
);
}
+227 -270
View File
@@ -2,13 +2,10 @@
import Image from "next/image";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import type { UseFormReturn } from "react-hook-form";
import type { HttpResponse, Upload } from "tus-js-client";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Button,
FileUploader,
Input,
Label,
@@ -21,10 +18,11 @@ import {
TableHead,
TableRow,
} from "@common/components";
import { FormWrapper } from "@/widgets/form-wrapper";
import { CodeSelect } from "@/entities/code";
import { AttachmentApi } from "@/entities/attachment";
import { ENDPOINT } from "@/shared/config";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
import { FormControl, FormField, FormItem } from "@/shared/ui/form";
import type { ImageFormData } from "../model/image-interface";
import { handleUpload } from "../lib/utils";
@@ -35,13 +33,10 @@ type ImageFormProps = {
handleDelete?: () => Promise<void>;
};
function ImageForm({ form, imgSn, handleSave, handleDelete }: ImageFormProps) {
const isModifyMode = !!imgSn;
function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
const atflYn = form.watch("atflYn");
const atflCd = form.watch("atflCd");
const router = useRouter();
const { confirm } = useConfirm();
const [imageUrl, setImageUrl] = useState("");
const [upload, setUpload] = useState<Upload | null>(null);
const [progress, setProgress] = useState(0);
@@ -53,22 +48,6 @@ function ImageForm({ form, imgSn, handleSave, handleDelete }: ImageFormProps) {
enabled: !!atflCd,
});
const onSubmit = async (data: ImageFormData) => {
const confirmMessage = isModifyMode
? "수정하시겠습니까?"
: "저장하시겠습니까?";
if (await confirm(confirmMessage)) {
await handleSave(data);
}
};
const onDelete = async () => {
if (await confirm("삭제하시겠습니까?")) {
await handleDelete?.();
}
};
const handleProgress = (percentage: string) => {
setProgress(Number(percentage));
};
@@ -91,255 +70,233 @@ function ImageForm({ form, imgSn, handleSave, handleDelete }: ImageFormProps) {
}, [atflId]);
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<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="imgTtl"
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="sortSeq"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
type="number"
min={0}
value={field.value ?? ""}
onChange={(e) => {
const value = e.target.value;
if (isNaN(Number(value))) return;
field.onChange(Number(value));
}}
className="w-[12rem] !text-[1.3rem]"
placeholder="정렬순서를 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>이미지유형</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="imgTypeCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
value={field.value ?? ""}
upCd="IMGTYPE"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>첨부파일</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="atflYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="atflYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="atflY" />
<Label htmlFor="atflY">이미지파일</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="atflN" />
<Label htmlFor="atflN">외부링크</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
{atflYn === "Y" && (
<TableRow>
<TableHead>이미지파일</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="atflCd"
render={() => (
<FormItem className="flex flex-col">
<FormControl>
<FileUploader
id="atflCd"
className="!h-auto !max-w-none flex-col !items-start"
accept=".jpeg,.jpg,.png"
onUpload={(e) => {
const files = e.target.files;
if (!files) return;
handleUpload(files, {
handleUploadStart: setUpload,
handleProgress,
handleSuccess: setUpload,
handleResponse: handleUploadResponse,
});
}}
customPreview={
<>
<Label htmlFor="atflCd">
<div className="relative h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
<p className="absolute bottom-3 left-0 p-2 text-base leading-none">
{(upload?.file as File)?.name}
</p>
<div className="relative h-full w-full">
{imageUrl && (
<Image
src={imageUrl}
alt="업로드된 이미지"
fill
objectFit="contain"
/>
)}
</div>
<Progress
value={progress}
className="absolute bottom-0 left-0"
<FormWrapper form={form} isModifyMode={!!imgSn} {...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="imgTtl"
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="sortSeq"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
type="number"
min={0}
value={field.value ?? ""}
onChange={(e) => {
const value = e.target.value;
if (isNaN(Number(value))) return;
field.onChange(Number(value));
}}
className="w-[12rem] !text-[1.3rem]"
placeholder="정렬순서를 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>이미지유형</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="imgTypeCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
value={field.value ?? ""}
upCd="IMGTYPE"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>첨부파일</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="atflYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="atflYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="atflY" />
<Label htmlFor="atflY">이미지파일</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="atflN" />
<Label htmlFor="atflN">외부링크</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
{atflYn === "Y" && (
<TableRow>
<TableHead>이미지파일</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="atflCd"
render={() => (
<FormItem className="flex flex-col">
<FormControl>
<FileUploader
id="atflCd"
className="!h-auto !max-w-none flex-col !items-start"
accept=".jpeg,.jpg,.png"
onUpload={(e) => {
const files = e.target.files;
if (!files) return;
handleUpload(files, {
handleUploadStart: setUpload,
handleProgress,
handleSuccess: setUpload,
handleResponse: handleUploadResponse,
});
}}
customPreview={
<>
<Label htmlFor="atflCd">
<div className="relative h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
<p className="absolute bottom-3 left-0 p-2 text-base leading-none">
{(upload?.file as File)?.name}
</p>
<div className="relative h-full w-full">
{imageUrl && (
<Image
src={imageUrl}
alt="업로드된 이미지"
fill
objectFit="contain"
/>
</div>
</Label>
<div className="mb-2 text-lg font-medium text-label">
<ul className="ml-10 list-disc">
<li>권장 사이즈: 1920x500</li>
<li>파일 최대 크기: 2MB</li>
<li>확장자: jpg, jpeg, png 가능</li>
</ul>
)}
</div>
</>
}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
)}
{atflYn === "N" && (
<TableRow>
<TableHead>외부링크</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lnkgUrlAddr"
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="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>
<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>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
<Progress
value={progress}
className="absolute bottom-0 left-0"
/>
</div>
</Label>
<div className="mb-2 text-lg font-medium text-label">
<ul className="ml-10 list-disc">
<li>권장 사이즈: 1920x500</li>
<li>파일 최대 크기: 2MB</li>
<li>확장자: jpg, jpeg, png 가능</li>
</ul>
</div>
</>
}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
)}
</div>
</form>
</Form>
</>
{atflYn === "N" && (
<TableRow>
<TableHead>외부링크</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lnkgUrlAddr"
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="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>
);
}
+34 -55
View File
@@ -1,9 +1,7 @@
import { useState } from "react";
import type { UseFormReturn } from "react-hook-form";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components";
import { Form } from "@/shared/ui/form";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import type { MenuFormData } from "../model/menu-interface";
import { BaseMenuForm } from "./base-menu-form";
import { TypeMenuForm } from "./type-menu-form";
@@ -33,13 +31,6 @@ function MenuForm({
const isModifyMode = !!menuCd;
const { confirm } = useConfirm();
const [onConfirm, setOnConfirm] = useState(false);
const handleConfirm = async (callback: () => Promise<void>) => {
setOnConfirm(true);
await callback();
setOnConfirm(false);
};
const onSubmit = async (data: MenuFormData) => {
if (isModifyMode) {
@@ -50,65 +41,53 @@ function MenuForm({
};
const onCreate = async (data: MenuFormData) => {
await handleConfirm(async () => {
if (await confirm("저장하시겠습니까?")) {
setOnConfirm(false);
handleCreate(data);
}
});
if (await confirm("저장하시겠습니까?")) {
handleCreate(data);
}
};
const onUpdate = async (data: MenuFormData) => {
await handleConfirm(async () => {
if (await confirm("수정하시겠습니까?")) {
setOnConfirm(false);
handleUpdate({ menuCd: menuCd!, data });
}
});
if (await confirm("수정하시겠습니까?")) {
handleUpdate({ menuCd: menuCd!, data });
}
};
const onDelete = () => {
handleConfirm(async () => {
if (await confirm("삭제하시겠습니까?")) {
setOnConfirm(false);
handleDelete?.(menuCd!);
}
});
const onDelete = async () => {
if (await confirm("삭제하시겠습니까?")) {
handleDelete?.(menuCd!);
}
};
const menuType = form.watch("menuLnkgTypeCd");
const isSubmitDisabled = !form.watch("siteId");
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
</h3>
<PermissionsManagementDialog menuCd={menuCd} />
</div>
<div className="card">
<BaseMenuForm form={form} menuCd={menuCd} />
<TypeMenuForm form={form} menuType={menuType} />
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
</h3>
<PermissionsManagementDialog menuCd={menuCd} />
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button size="lg" type="submit" disabled={isSubmitDisabled}>
저장
<div className="card">
<BaseMenuForm form={form} menuCd={menuCd} />
<TypeMenuForm form={form} menuType={menuType} />
</div>
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button size="lg" type="submit" disabled={isSubmitDisabled}>
저장
</Button>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
)}
</div>
</form>
</Form>
<ConfirmDialog open={onConfirm} />
</>
)}
</div>
</form>
</Form>
);
}
+2 -2
View File
@@ -29,9 +29,9 @@ function CreatePage() {
faxNo: "",
telNo: "",
lwndCn: "",
bscSiteYn: "",
bscSiteYn: "Y",
bkmkIcon: "",
useYn: "",
useYn: "Y",
siteSkn: "",
},
});
@@ -32,7 +32,6 @@ import {
type Domain,
type DomainFormData,
} from "../model/domain-interface";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { useAlertStore } from "@/shared/lib/use-alert-store";
type DomainManagementDialogProps = {
@@ -79,7 +78,6 @@ function DomainManagementDialog({
name: "domains",
});
const [onConfirm, setOnConfirm] = useState(false);
const [selectedRows, setSelectedRows] = useState<number[]>([]);
const initialForm = () => {
@@ -88,14 +86,11 @@ function DomainManagementDialog({
};
const onSubmit = async (data: DomainFormData) => {
setOnConfirm(true);
if (await confirm("저장하시겠습니까?")) {
setOnConfirm(false);
await mutateAsync(data.domains);
handleSaveDomain?.(data.domains);
alert("저장되었습니다.");
}
setOnConfirm(false);
};
const handleAddDomain = () => {
@@ -153,160 +148,157 @@ function DomainManagementDialog({
fields.length > 0 && selectedRows.length === fields.length;
return (
<>
<Dialog {...props}>
<DialogTrigger asChild>
<Button>도메인 관리</Button>
</DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>도메인 관리</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
>
<div className="mb-4 flex justify-end gap-2">
<Button type="button" size="sm" onClick={handleAddDomain}>
항목 추가
<Dialog {...props}>
<DialogTrigger asChild>
<Button>도메인 관리</Button>
</DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>도메인 관리</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
>
<div className="mb-4 flex justify-end gap-2">
<Button type="button" size="sm" onClick={handleAddDomain}>
항목 추가
</Button>
<Button
type="button"
size="sm"
color="red"
onClick={handleDeleteDomain}
disabled={selectedRows.length === 0}
>
항목 삭제
</Button>
</div>
<div className="card card-border max-h-[28.5rem] overflow-auto">
<Table variant="secondary">
<colgroup>
<col width="10%" />
<col width="auto" />
<col width="15%" />
<col width="25%" />
</colgroup>
<TableHeader className="sticky -top-[0.6rem]">
<TableRow>
<TableHead className="!px-0">
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
/>
</TableHead>
<TableHead>도메인 주소</TableHead>
<TableHead>대표 URL</TableHead>
<TableHead>사용여부</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{fields.map((field, i) => {
if (field.mode === "D") return null;
return (
<TableRow key={field.id}>
<TableCell align="center" className="!px-0">
<Checkbox
checked={selectedRows.includes(i)}
onCheckedChange={() => handleSelectRow(i)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`domains.${i}.dmnAddr`}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`domains.${i}.rprsDmnYn`}
render={({ field }) => (
<FormItem>
<FormControl>
<Checkbox
checked={field.value === "Y"}
onCheckedChange={(checked) => {
field.onChange(checked ? "Y" : "N");
handleChangeRprsDmnYn(i);
}}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`domains.${i}.useYn`}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="flex justify-center"
>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="Y"
id={`useY-${i}`}
/>
<Label htmlFor={`useY-${i}`}>예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="N"
id={`useN-${i}`}
/>
<Label htmlFor={`useN-${i}`}>
아니오
</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
<DialogFooter className="!justify-center">
<Button type="submit" size="lg">
저장
</Button>
<DialogClose asChild>
<Button type="button" size="lg" color="white">
취소
</Button>
<Button
type="button"
size="sm"
color="red"
onClick={handleDeleteDomain}
disabled={selectedRows.length === 0}
>
항목 삭제
</Button>
</div>
<div className="card card-border max-h-[28.5rem] overflow-auto">
<Table variant="secondary">
<colgroup>
<col width="10%" />
<col width="auto" />
<col width="15%" />
<col width="25%" />
</colgroup>
<TableHeader className="sticky -top-[0.6rem]">
<TableRow>
<TableHead className="!px-0">
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
/>
</TableHead>
<TableHead>도메인 주소</TableHead>
<TableHead>대표 URL</TableHead>
<TableHead>사용여부</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{fields.map((field, i) => {
if (field.mode === "D") return null;
return (
<TableRow key={field.id}>
<TableCell align="center" className="!px-0">
<Checkbox
checked={selectedRows.includes(i)}
onCheckedChange={() => handleSelectRow(i)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`domains.${i}.dmnAddr`}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`domains.${i}.rprsDmnYn`}
render={({ field }) => (
<FormItem>
<FormControl>
<Checkbox
checked={field.value === "Y"}
onCheckedChange={(checked) => {
field.onChange(checked ? "Y" : "N");
handleChangeRprsDmnYn(i);
}}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`domains.${i}.useYn`}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="flex justify-center"
>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="Y"
id={`useY-${i}`}
/>
<Label htmlFor={`useY-${i}`}>예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="N"
id={`useN-${i}`}
/>
<Label htmlFor={`useN-${i}`}>
아니오
</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
<DialogFooter className="!justify-center">
<Button type="submit" size="lg">
저장
</Button>
<DialogClose asChild>
<Button type="button" size="lg" color="white">
취소
</Button>
</DialogClose>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
<ConfirmDialog open={onConfirm} />
</>
</DialogClose>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}
+129 -137
View File
@@ -28,7 +28,6 @@ import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
import { ENDPOINT } from "@/shared/config";
import { IpApi } from "../api/ip-service";
import { ipFormSchema, type IP, type IPFormData } from "../model/ip-interface";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { useAlertStore } from "@/shared/lib/use-alert-store";
type IPManagementDialogProps = {
@@ -75,7 +74,6 @@ function IPManagementDialog({
name: "ips",
});
const [onConfirm, setOnConfirm] = useState(false);
const [selectedRows, setSelectedRows] = useState<number[]>([]);
const initialForm = () => {
@@ -84,13 +82,10 @@ function IPManagementDialog({
};
const onSubmit = async (data: IPFormData) => {
setOnConfirm(true);
if (await confirm("저장하시겠습니까?")) {
setOnConfirm(false);
await mutateAsync(data.ips);
alert("저장되었습니다.");
}
setOnConfirm(false);
};
const handleAddIP = () => {
@@ -140,139 +135,136 @@ function IPManagementDialog({
fields.length > 0 && selectedRows.length === fields.length;
return (
<>
<Dialog {...props}>
<DialogTrigger asChild>
<Button disabled={triggerDisabled}>IP 관리</Button>
</DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>IP 관리</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
>
<div className="mb-4 flex justify-end gap-2">
<Button type="button" size="sm" onClick={handleAddIP}>
항목 추가
<Dialog {...props}>
<DialogTrigger asChild>
<Button disabled={triggerDisabled}>IP 관리</Button>
</DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>IP 관리</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
>
<div className="mb-4 flex justify-end gap-2">
<Button type="button" size="sm" onClick={handleAddIP}>
항목 추가
</Button>
<Button
type="button"
size="sm"
color="red"
onClick={handleDeleteIP}
disabled={selectedRows.length === 0}
>
항목 삭제
</Button>
</div>
<div className="card card-border max-h-[28.5rem] overflow-auto">
<Table variant="secondary">
<colgroup>
<col width="10%" />
<col width="65%" />
<col width="25%" />
</colgroup>
<TableHeader className="sticky -top-[0.6rem]">
<TableRow>
<TableHead className="!px-0">
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
/>
</TableHead>
<TableHead>IP 주소</TableHead>
<TableHead>허용여부</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{fields.map((field, i) => {
if (field.mode === "D") return null;
return (
<TableRow key={field.id}>
<TableCell align="center" className="!px-0">
<Checkbox
checked={selectedRows.includes(i)}
onCheckedChange={() => handleSelectRow(i)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`ips.${i}.ipAddr`}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`ips.${i}.prmYn`}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="flex justify-center"
>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="Y"
id={`prmY-${i}`}
/>
<Label htmlFor={`prmY-${i}`}>예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="N"
id={`prmN-${i}`}
/>
<Label htmlFor={`prmN-${i}`}>
아니오
</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
<DialogFooter className="!justify-center">
<Button type="submit" size="lg">
저장
</Button>
<DialogClose asChild>
<Button type="button" size="lg" color="white">
취소
</Button>
<Button
type="button"
size="sm"
color="red"
onClick={handleDeleteIP}
disabled={selectedRows.length === 0}
>
항목 삭제
</Button>
</div>
<div className="card card-border max-h-[28.5rem] overflow-auto">
<Table variant="secondary">
<colgroup>
<col width="10%" />
<col width="65%" />
<col width="25%" />
</colgroup>
<TableHeader className="sticky -top-[0.6rem]">
<TableRow>
<TableHead className="!px-0">
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
/>
</TableHead>
<TableHead>IP 주소</TableHead>
<TableHead>허용여부</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{fields.map((field, i) => {
if (field.mode === "D") return null;
return (
<TableRow key={field.id}>
<TableCell align="center" className="!px-0">
<Checkbox
checked={selectedRows.includes(i)}
onCheckedChange={() => handleSelectRow(i)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`ips.${i}.ipAddr`}
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
<TableCell align="center">
<FormField
control={form.control}
name={`ips.${i}.prmYn`}
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="flex justify-center"
>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="Y"
id={`prmY-${i}`}
/>
<Label htmlFor={`prmY-${i}`}>예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem
value="N"
id={`prmN-${i}`}
/>
<Label htmlFor={`prmN-${i}`}>
아니오
</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
<DialogFooter className="!justify-center">
<Button type="submit" size="lg">
저장
</Button>
<DialogClose asChild>
<Button type="button" size="lg" color="white">
취소
</Button>
</DialogClose>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
<ConfirmDialog open={onConfirm} />
</>
</DialogClose>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}
+305 -362
View File
@@ -1,9 +1,6 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { type UseFormReturn } from "react-hook-form";
import {
Button,
Input,
Label,
RadioGroup,
@@ -14,10 +11,10 @@ import {
TableHead,
TableRow,
} from "@common/components";
import { FormWrapper } from "@/widgets/form-wrapper";
import { CodeSelect } from "@/entities/code";
import type { SiteFormData } from "@/entities/site";
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
import type { Domain } from "../model/domain-interface";
import { IPManagementDialog } from "./ip-management-dialog";
import { DomainManagementDialog } from "./domain-management-dialog";
@@ -29,21 +26,12 @@ type SiteFormProps = {
handleDelete?: () => Promise<void>;
};
function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) {
function SiteForm({ form, siteId, ...props }: SiteFormProps) {
const isModifyMode = !!siteId;
const router = useRouter();
const { confirm } = useConfirm();
const [onConfirm, setOnConfirm] = useState(false);
const [isIpManagementOpen, setIsIpManagementOpen] = useState(false);
const [isDomainManagementOpen, setIsDomainManagementOpen] = useState(false);
const handleConfirm = async (callback: () => Promise<void>) => {
setOnConfirm(true);
await callback();
setOnConfirm(false);
};
const handleSaveDomain = (rows: Domain[]) => {
form.setValue(
"dmnAddr",
@@ -51,359 +39,314 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) {
);
};
const onSubmit = async (data: SiteFormData) => {
const confirmMessage = isModifyMode
? "수정하시겠습니까?"
: "저장하시겠습니까?";
await handleConfirm(async () => {
if (await confirm(confirmMessage)) {
setOnConfirm(false);
handleSave(data);
}
});
};
const onDelete = async () => {
await handleConfirm(async () => {
if (await confirm("삭제하시겠습니까?")) {
setOnConfirm(false);
handleDelete?.();
}
});
};
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<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="siteId"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="w-[12rem] !text-[1.3rem]"
disabled={isModifyMode}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 구분</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<FormField
control={form.control}
name="siteKndCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
upCd="SITEKNDCD"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
{isModifyMode && (
<IPManagementDialog
siteId={siteId}
triggerDisabled={form.watch("siteKndCd") === "O"}
open={isIpManagementOpen}
onOpenChange={setIsIpManagementOpen}
<FormWrapper form={form} isModifyMode={isModifyMode} {...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="siteId"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="w-[12rem] !text-[1.3rem]"
disabled={isModifyMode}
/>
)}
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 디자인</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteSkn"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
value={field.value ?? ""}
upCd="SITESKINCD"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 구분</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<FormField
control={form.control}
name="siteKndCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
upCd="SITEKNDCD"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
{isModifyMode && (
<IPManagementDialog
siteId={siteId}
triggerDisabled={form.watch("siteKndCd") === "O"}
open={isIpManagementOpen}
onOpenChange={setIsIpManagementOpen}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteNm"
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="siteExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="사이트 설명을 입력하십시오."
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
{isModifyMode && (
<TableRow>
<TableHead>사이트 도메인</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<div className="h-[3.2rem] w-[21.5rem] cursor-not-allowed rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{form.watch("dmnAddr")}
</div>
<DomainManagementDialog
siteId={siteId}
open={isDomainManagementOpen}
onOpenChange={setIsDomainManagementOpen}
handleSaveDomain={handleSaveDomain}
)}
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 디자인</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteSkn"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
value={field.value ?? ""}
upCd="SITESKINCD"
onValueChange={field.onChange}
/>
</div>
</TableCell>
</TableRow>
)}
<TableRow>
<TableHead>사이트 하단 표기 주소</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteAddr"
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="telNo"
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="faxNo"
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="lwndCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="사이트 표기 내용을 입력하십시오. (ex. COPYRIGHT c 2013 KECO. ALL RIGHTS RESERVED.)"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>북마크 아이콘</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bkmkIcon"
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="bscSiteYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="bscSiteYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="bscSiteY" />
<Label htmlFor="bscSiteY">예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="bscSiteN" />
<Label htmlFor="bscSiteN">아니오</Label>
</div>
</RadioGroup>
</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>
<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>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteNm"
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="siteExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="사이트 설명을 입력하십시오."
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
<TableRow>
<TableHead>사이트 도메인</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<div className="h-[3.2rem] w-[21.5rem] cursor-not-allowed rounded-md border border-input bg-form px-3 py-2 text-[1.3rem] opacity-50">
{form.watch("dmnAddr")}
</div>
<DomainManagementDialog
siteId={siteId}
open={isDomainManagementOpen}
onOpenChange={setIsDomainManagementOpen}
handleSaveDomain={handleSaveDomain}
/>
</div>
</TableCell>
</TableRow>
)}
</div>
</form>
</Form>
<ConfirmDialog open={onConfirm} />
</>
<TableRow>
<TableHead>사이트 하단 표기 주소</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteAddr"
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="telNo"
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="faxNo"
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="lwndCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="사이트 표기 내용을 입력하십시오. (ex. COPYRIGHT c 2013 KECO. ALL RIGHTS RESERVED.)"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>북마크 아이콘</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bkmkIcon"
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="bscSiteYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="bscSiteYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="bscSiteY" />
<Label htmlFor="bscSiteY">예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="bscSiteN" />
<Label htmlFor="bscSiteN">아니오</Label>
</div>
</RadioGroup>
</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>
);
}
@@ -0,0 +1 @@
export { FormWrapper } from "./ui/form-wrapper";
@@ -0,0 +1,68 @@
import { useRouter } from "next/navigation";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { type FieldValues, type UseFormReturn } from "react-hook-form";
import { Button } from "@common/components/ui";
import { Form } from "@/shared/ui/form";
type FormWrapperProps<T extends FieldValues> = {
form: UseFormReturn<T>;
children: React.ReactNode;
isModifyMode?: boolean;
handleSave: (data: T) => Promise<void>;
handleDelete?: () => Promise<void>;
};
function FormWrapper<T extends FieldValues>({
form,
children,
isModifyMode,
handleSave,
handleDelete,
}: FormWrapperProps<T>) {
const router = useRouter();
const { confirm } = useConfirm();
const onSubmit = async (data: T) => {
const confirmMessage = isModifyMode
? "수정하시겠습니까?"
: "저장하시겠습니까?";
if (await confirm(confirmMessage)) {
await handleSave(data);
}
};
const onDelete = async () => {
if (await confirm("삭제하시겠습니까?")) {
await handleDelete?.();
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{children}
<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>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
)}
</div>
</form>
</Form>
);
}
export { FormWrapper };