feat: 사이트 수정 작업

This commit is contained in:
2025-03-14 16:57:19 +09:00
parent d4a2b5641d
commit 9277ffbde9
9 changed files with 487 additions and 446 deletions
+3 -3
View File
@@ -1,10 +1,10 @@
import { RegisterPage as ViewRegisterPage } from "@/pages/site";
import { ModifyPage as VModifyPage } from "@/pages/site";
export default async function RegisterPage({
export default async function ModifyPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <ViewRegisterPage siteId={id} />;
return <VModifyPage siteId={id} />;
}
@@ -5,7 +5,7 @@ import type {
SiteService,
ISiteDetail,
ISiteParams,
ISiteData,
SiteFormData,
} from "../model/site-interface";
export class SiteApiService implements SiteService {
@@ -24,7 +24,7 @@ export class SiteApiService implements SiteService {
return response.data;
}
public async modifySite(siteId: string, data: ISiteData): Promise<void> {
public async modifySite(siteId: string, data: SiteFormData): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`,
data,
@@ -1,3 +1,5 @@
import { z } from "zod";
export interface ISiteParams {
siteNm: string;
siteKndCd: string;
@@ -47,21 +49,7 @@ export interface ISiteInfo {
empty: boolean;
}
export interface ISiteData {
siteNm: string;
siteExpln: string;
siteKndCd: string;
siteAddr: string;
faxNo: string;
telNo: string;
lwndCn: string;
bscSiteYn: string;
bkmkIcon: string;
useYn: string;
siteSkn: string;
}
export interface ISiteDetail extends ISiteData {
export interface ISiteDetail extends SiteFormData {
siteId: string;
siteKindNm: string;
basicSiteYn: string;
@@ -74,8 +62,25 @@ export interface ISiteDetail extends ISiteData {
mdFrNm: string;
}
export const siteFormSchema = z.object({
siteId: z.string().max(2),
siteNm: z.string().max(100),
siteExpln: z.string().max(1000).optional(),
siteKndCd: z.string().max(10),
siteAddr: z.string().max(2000).optional(),
faxNo: z.string().max(100).optional(),
telNo: z.string().max(1000).optional(),
lwndCn: z.string().max(2000).optional(),
bscSiteYn: z.string().optional(),
bkmkIcon: z.string().max(100).optional(),
useYn: z.string().optional(),
siteSkn: z.string().max(10).optional(),
});
export type SiteFormData = z.infer<typeof siteFormSchema>;
export interface SiteService {
siteInfo(query: ISiteParams): Promise<ISiteInfo>;
siteDetail(siteId: string): Promise<ISiteDetail>;
modifySite(siteId: string, data: ISiteData): Promise<void>;
modifySite(siteId: string, data: SiteFormData): Promise<void>;
}
+2 -1
View File
@@ -1,2 +1,3 @@
export { RegisterPage } from "./ui/register-page";
export { CreatePage } from "./ui/create-page";
export { ModifyPage } from "./ui/modify-page";
export { SitePage } from "./ui/site-page";
@@ -1,17 +0,0 @@
import type { ISiteData } from "@/entities/site";
const siteFormSchema: ISiteData = {
siteNm: "",
siteExpln: "",
siteKndCd: "",
siteAddr: "",
faxNo: "",
telNo: "",
lwndCn: "",
bscSiteYn: "",
bkmkIcon: "",
useYn: "",
siteSkn: "",
};
export { siteFormSchema };
+72
View File
@@ -0,0 +1,72 @@
"use client";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useMutation, useQuery } from "@tanstack/react-query";
import { zodResolver } from "@hookform/resolvers/zod";
import {
SiteApi,
siteFormSchema,
type ISiteDetail,
type SiteFormData,
} from "@/entities/site";
import { ENDPOINT } from "@/shared/config/api";
import { SitePageLayout } from "./layout";
import { SiteForm } from "./site-form";
function ModifyPage({ siteId }: { siteId: string }) {
const isModifyMode = !!siteId;
const { data: siteDetail } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId],
queryFn: () => SiteApi.siteDetail(siteId),
select: (data) => ({
...data,
faxNo: data.faxNumber,
bscSiteYn: data.basicSiteYn,
}),
enabled: isModifyMode,
});
const { mutateAsync: modifySite } = useMutation({
mutationFn: (data: SiteFormData) => SiteApi.modifySite(siteId, data),
onSuccess: (data) => {
console.log("success!", data);
},
});
const form = useForm<SiteFormData>({
defaultValues: siteFormSchema.omit({ siteId: true }).parse({
siteNm: "",
siteKndCd: "",
}),
resolver: zodResolver(siteFormSchema),
});
const handleSave = (data: SiteFormData) => {
console.log(data);
modifySite(data);
};
useEffect(() => {
if (!siteDetail) return;
const keys = Object.keys(siteFormSchema.shape);
for (const key of keys) {
form.setValue(
key as keyof SiteFormData,
siteDetail[key as keyof ISiteDetail],
);
}
}, [siteDetail]);
console.log(form.watch());
return (
<SitePageLayout>
<SiteForm form={form} siteId={siteId} handleSave={handleSave} />
</SitePageLayout>
);
}
export { ModifyPage };
-406
View File
@@ -1,406 +0,0 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { useQuery } from "@tanstack/react-query";
import {
Button,
Input,
Label,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@common/components";
import { SiteApi, type ISiteDetail } from "@/entities/site";
import { CodeSelect } from "@/entities/code";
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
import { ENDPOINT } from "@/shared/config/api";
import { SitePageLayout } from "./layout";
import { IPManagementDialog } from "./ip-management-dialog";
import {
DomainManagementDialog,
type Domain,
} from "./domain-management-dialog";
import { siteFormSchema } from "../model/form-schema";
type CreateFormData = typeof siteFormSchema & {
siteId: string;
};
type ModifyFormData = typeof siteFormSchema;
type FormData = CreateFormData | ModifyFormData;
type ViewState = Partial<ISiteDetail>;
function RegisterPage({ siteId }: { siteId: string }) {
const isModifyMode = !!siteId;
const router = useRouter();
const { data: siteDetail } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId],
queryFn: () => SiteApi.siteDetail(siteId),
select: (data) => {
const newData = {
...data,
faxNo: data.faxNumber,
bscSiteYn: data.basicSiteYn,
};
handleViewStateChange("siteKndCd")(data.siteKndCd);
handleViewStateChange("siteSkn")(data.siteSkn);
return newData;
},
enabled: isModifyMode,
});
const form = useForm<FormData>({
defaultValues: isModifyMode
? siteFormSchema
: { ...siteFormSchema, siteId: "" },
});
const [viewState, setViewState] = useState<ViewState>({});
const [representativeDomain, setRepresentativeDomain] = useState<string>("");
const handleViewStateChange = (key: keyof ISiteDetail) => (value: string) => {
setViewState({ ...viewState, [key]: value });
};
const handleDomainSave = (rows: Domain[]) => {
setRepresentativeDomain(
rows.find((row) => row.isRepresentative)?.domainUrl || "",
);
};
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<Form {...form}>
<SitePageLayout>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
사이트 등록/수정
</h3>
</div>
<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
className="w-[12rem] !text-[1.3rem]"
defaultValue={siteDetail?.siteId}
disabled={isModifyMode}
{...field}
/>
</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
upCd="SITEKNDCD"
defaultValue={siteDetail?.siteKndCd}
onValueChange={(value) => {
handleViewStateChange("siteKndCd")(value);
field.onChange(value);
}}
/>
</FormControl>
</FormItem>
)}
/>
{isModifyMode && (
<IPManagementDialog
triggerDisabled={viewState.siteKndCd === "O"}
/>
)}
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 디자인</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteSkn"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
upCd="SITESKINCD"
defaultValue={siteDetail?.siteSkn}
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트명을 입력하십시오."
defaultValue={siteDetail?.siteNm}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 설명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 설명을 입력하십시오."
defaultValue={siteDetail?.siteExpln}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow className={`${!isModifyMode ? "hidden" : ""}`}>
<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">
{representativeDomain}
</div>
<DomainManagementDialog handleSave={handleDomainSave} />
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 주소</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteAddr"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 주소를 입력하십시오."
defaultValue={siteDetail?.siteAddr}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 전화번호</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="telNo"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 전화번호를 입력하십시오."
defaultValue={siteDetail?.telNo}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 팩스번호</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="faxNo"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 하단 팩스번호를 입력하십시오."
defaultValue={siteDetail?.faxNo}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 내용</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lwndCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="사이트 표기 내용을 입력하십시오. (ex. COPYRIGHT c 2013 KECO. ALL RIGHTS RESERVED.)"
defaultValue={siteDetail?.lwndCn}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>북마크 아이콘</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bkmkIcon"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
className="!text-[1.3rem]"
placeholder="아이콘 파일 경로 및 파일명을 입력하여 주십시오."
defaultValue={siteDetail?.bkmkIcon}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>기본 사이트 여부</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bscSiteYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
name="bscSiteYn"
defaultValue={siteDetail?.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
name="useYn"
defaultValue={siteDetail?.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>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button size="lg" type="submit">
저장
</Button>
<Button size="lg" color="white" onClick={() => router.back()}>
취소
</Button>
<Button size="lg" color="red">
삭제
</Button>
</div>
</form>
</SitePageLayout>
</Form>
);
}
export { RegisterPage };
+386
View File
@@ -0,0 +1,386 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { type UseFormReturn } from "react-hook-form";
import {
Button,
Input,
Label,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@common/components";
import { CodeSelect } from "@/entities/code";
import { type ISiteDetail, type SiteFormData } from "@/entities/site";
import {
FormItem,
FormControl,
FormField,
Form,
FormMessage,
} from "@/shared/ui/form";
import { IPManagementDialog } from "./ip-management-dialog";
import {
DomainManagementDialog,
type Domain,
} from "./domain-management-dialog";
type SiteFormProps = {
form: UseFormReturn<SiteFormData>;
siteId?: string;
handleSave: (data: SiteFormData) => void;
};
function SiteForm({ form, siteId, handleSave }: SiteFormProps) {
const router = useRouter();
const [viewState, setViewState] = useState<Partial<ISiteDetail>>({});
const [representativeDomain, setRepresentativeDomain] = useState<string>("");
const isModifyMode = !!siteId;
const handleViewStateChange = (key: keyof ISiteDetail) => (value: string) => {
setViewState({ ...viewState, [key]: value });
};
const handleDomainSave = (rows: Domain[]) => {
setRepresentativeDomain(
rows.find((row) => row.isRepresentative)?.domainUrl || "",
);
};
const onSubmit = (data: SiteFormData) => {
handleSave(data);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
사이트 등록/수정
</h3>
</div>
<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}
className="w-[12rem] !text-[1.3rem]"
disabled={isModifyMode}
/>
</FormControl>
<FormMessage />
</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={(value) => {
handleViewStateChange("siteKndCd")(value);
field.onChange(value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{isModifyMode && (
<IPManagementDialog
triggerDisabled={viewState.siteKndCd === "O"}
/>
)}
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 디자인</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteSkn"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
upCd="SITESKINCD"
onValueChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
placeholder="사이트명을 입력하십시오."
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 설명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
placeholder="사이트 설명을 입력하십시오."
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow className={`${!isModifyMode ? "hidden" : ""}`}>
<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">
{representativeDomain}
</div>
<DomainManagementDialog handleSave={handleDomainSave} />
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 주소</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteAddr"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
placeholder="사이트 하단 주소를 입력하십시오."
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 전화번호</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="telNo"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
placeholder="사이트 하단 전화번호를 입력하십시오."
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 팩스번호</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="faxNo"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
placeholder="사이트 하단 팩스번호를 입력하십시오."
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 내용</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lwndCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
placeholder="사이트 표기 내용을 입력하십시오. (ex. COPYRIGHT c 2013 KECO. ALL RIGHTS RESERVED.)"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>북마크 아이콘</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bkmkIcon"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
className="!text-[1.3rem]"
placeholder="아이콘 파일 경로 및 파일명을 입력하여 주십시오."
/>
</FormControl>
<FormMessage />
</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>
<FormMessage />
</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>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button size="lg" type="submit">
저장
</Button>
<Button size="lg" color="white" onClick={() => router.back()}>
취소
</Button>
<Button size="lg" color="red">
삭제
</Button>
</div>
</form>
</Form>
);
}
export { SiteForm };
+1 -1
View File
@@ -154,7 +154,7 @@ const FormMessage = React.forwardRef<
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
className={cn("text-[1rem] font-medium text-destructive", className)}
{...props}
>
{body}