feat: 회원수정 폼 작업

This commit is contained in:
2025-06-13 17:37:13 +09:00
parent 0c658ee5da
commit 4b7d4f0cc9
8 changed files with 180 additions and 69 deletions
@@ -0,0 +1,10 @@
import { ModifyPage } from "@/pages/users/ui/modify-page";
export default async function UserModifyPage({
params,
}: {
params: Promise<{ userUnqId: string }>;
}) {
const { userUnqId } = await params;
return <ModifyPage userUnqId={userUnqId} />;
}
@@ -1,6 +1,6 @@
import { UserDetail as VUserDetail } from "@/pages/users";
export default async function UserDetail({
export default async function UserDetailPage({
params,
}: {
params: Promise<{ userUnqId: string }>;
+2
View File
@@ -3,9 +3,11 @@ export {
USER_STATE_CODE,
userSaveSchema,
userPasswordUpdateSchema,
userUpdateSchema,
type UserSearchParams,
type UserListResponse,
type UserSaveData,
type UserDetailResponse,
type UserPasswordUpdateData,
type UserUpdateData,
} from "./model/user-interface";
+1 -10
View File
@@ -42,21 +42,12 @@ function CreatePage() {
},
});
// 저장 처리 함수
const handleSave = async (data: UserSaveData) => {
try {
await saveUser(data);
} catch (error) {
console.error("회원 등록 실패:", error);
}
};
return (
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">회원 등록</h3>
</div>
<UsersForm form={form} handleSave={handleSave} />
<UsersForm form={form} handleSave={saveUser} />
</div>
);
}
@@ -0,0 +1,75 @@
"use client";
import React from "react";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import {
UserApi,
userUpdateSchema,
type UserUpdateData,
} from "@/entities/user";
import { ENDPOINT } from "@/shared/config";
import { useMutationWrapper, useAlertStore } from "@/shared/lib";
import { UsersForm } from "./users-form";
interface ModifyPageProps {
userUnqId: string;
}
export function ModifyPage({ userUnqId }: ModifyPageProps) {
const router = useRouter();
const alert = useAlertStore((state) => state.setMessage);
const { data: userData } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
queryFn: () => UserApi.findByUserUnqId(userUnqId),
enabled: !!userUnqId,
});
const form = useForm<UserUpdateData>({
resolver: zodResolver(userUpdateSchema),
defaultValues: {
userId: userData?.userId ?? "",
userName: userData?.userName ?? "",
email: userData?.email ?? "",
userStateCode: userData?.userStateCode ?? "",
pswdHnt: userData?.pswdHnt ?? "",
pswdAns: userData?.pswdAns ?? "",
mphn: userData?.mphn ?? "",
telno: userData?.telno ?? "",
zip: userData?.zip ?? "",
addr: userData?.addr ?? "",
daddr: userData?.daddr ?? "",
laeSeCd: userData?.laeSeCd ?? "",
ogdpSeCd: userData?.ogdpSeCd ?? "",
aprvYn: userData?.aprvYn ?? "",
expryYmd: userData?.expryYmd ?? "",
instSeCd: userData?.instSeCd ?? "",
dtlInstCd: userData?.dtlInstCd ?? "",
aprvYmd: userData?.aprvYmd ?? "",
enoCd: userData?.enoCd ?? "",
},
});
const { mutateAsync: updateUser } = useMutationWrapper({
mutationFn: (data: UserUpdateData) => UserApi.updateUser(userUnqId, data),
onSuccess: () => {
alert("회원 정보가 수정되었습니다.", router.back);
},
});
return (
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">회원 정보 수정</h3>
</div>
<UsersForm
userId={userData?.userId}
form={form}
handleSave={updateUser}
/>
</div>
);
}
+5 -2
View File
@@ -1,7 +1,7 @@
"use client";
import dayjs from "dayjs";
import { useRouter } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
Button,
@@ -18,6 +18,7 @@ import { PasswordResetDialog } from "./password-reset-dialog";
function UserDetail({ userUnqId }: { userUnqId: string }) {
const router = useRouter();
const pathname = usePathname();
const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage);
@@ -157,7 +158,9 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
<Button size="lg" className="white" onClick={router.back}>
목록
</Button>
<Button size="lg">수정</Button>
<Button size="lg" onClick={() => router.push(`${pathname}/modify`)}>
수정
</Button>
<Button size="lg" color="red">
삭제
</Button>
+68 -34
View File
@@ -1,5 +1,10 @@
import { useState, useMemo, useCallback } from "react";
import { useWatch, type UseFormReturn } from "react-hook-form";
import {
useWatch,
type Path,
type PathValue,
type UseFormReturn,
} from "react-hook-form";
import { useQuery } from "@tanstack/react-query";
import {
Button,
@@ -17,7 +22,11 @@ import {
} from "@common/components";
import { FormWrapper } from "@/widgets/form-wrapper";
import { CodeApi, CodeSelect } from "@/entities/code";
import { UserApi, type UserSaveData } from "@/entities/user";
import {
UserApi,
type UserSaveData,
type UserUpdateData,
} from "@/entities/user";
import {
FormField,
FormItem,
@@ -29,19 +38,23 @@ import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { ZipSearchDialog } from "./zip-search-dialog";
import { AgencySearchDialog } from "./agency-search-dialog";
type UsersFormProps = {
type UsersFormProps<T extends UserSaveData | UserUpdateData> = {
userId?: string;
form: UseFormReturn<UserSaveData>;
handleSave: (data: UserSaveData) => Promise<void>;
form: UseFormReturn<T>;
handleSave: (data: T) => Promise<string>;
handleDelete?: () => Promise<void>;
};
function UsersForm({ userId, form, ...props }: UsersFormProps) {
function UsersForm<T extends UserSaveData | UserUpdateData>({
userId,
form,
...props
}: UsersFormProps<T>) {
const isModifyMode = !!userId;
const [watchOgdpSeCd, watchDtlInstCd] = useWatch({
control: form.control,
name: ["ogdpSeCd", "dtlInstCd"],
name: ["ogdpSeCd" as Path<T>, "dtlInstCd" as Path<T>],
});
const alert = useAlertStore((state) => state.setMessage);
@@ -132,21 +145,24 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
);
const handlePasswordConfirmChange = useCallback((value: string) => {
const password = form.getValues("password");
const password = form.getValues("password" as Path<T>);
setPasswordMatch(!password || value === password);
}, []);
const handleAddressSelect = useCallback(
(data: { zip: string; addr: string }) => {
form.setValue("zip", data.zip);
form.setValue("addr", data.addr);
form.setValue("zip" as Path<T>, data.zip as PathValue<T, Path<T>>);
form.setValue("addr" as Path<T>, data.addr as PathValue<T, Path<T>>);
},
[form],
);
const handleAgencySelect = useCallback(
(data: { lwpoCd: string; lwpoCdNm: string }) => {
form.setValue("instSeCd", data.lwpoCd);
form.setValue(
"instSeCd" as Path<T>,
data.lwpoCd as PathValue<T, Path<T>>,
);
setInstSeCdNm(data.lwpoCdNm);
},
[form],
@@ -167,7 +183,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="userId"
name={"userId" as Path<T>}
render={({ field }) => (
<div className="flex items-center gap-2">
<FormItem>
@@ -191,7 +207,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
className="px-6"
onClick={(e) => {
e.preventDefault();
checkDuplicateUserId(field.value);
checkDuplicateUserId(field.value as string);
}}
disabled={!field.value || isValidUserId}
>
@@ -211,7 +227,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="password"
name={"password" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -258,7 +274,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="pswdHnt"
name={"pswdHnt" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -281,7 +297,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="pswdAns"
name={"pswdAns" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -304,7 +320,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="userName"
name={"userName" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -327,7 +343,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell>
<FormField
control={form.control}
name="mphn"
name={"mphn" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -347,7 +363,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell>
<FormField
control={form.control}
name="telno"
name={"telno" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -370,7 +386,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="email"
name={"email" as Path<T>}
render={({ field }) => (
<div className="flex items-center gap-2">
<FormItem>
@@ -393,7 +409,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
onClick={(e) => {
e.preventDefault();
if (field.value) {
checkDuplicateEmail(field.value);
checkDuplicateEmail(field.value as string);
}
}}
disabled={!field.value || isValidEmail}
@@ -415,7 +431,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<div className="mb-2 flex items-center gap-2">
<FormField
control={form.control}
name="zip"
name={"zip" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -433,7 +449,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
</div>
<FormField
control={form.control}
name="addr"
name={"addr" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -454,7 +470,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell>
<FormField
control={form.control}
name="daddr"
name={"daddr" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -477,7 +493,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="ogdpSeCd"
name={"ogdpSeCd" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -487,9 +503,18 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
value={field.value ?? ""}
onValueChange={(value) => {
field.onChange(value);
form.setValue("dtlInstCd", "");
form.setValue("laeSeCd", "");
form.setValue("enoCd", "");
form.setValue(
"dtlInstCd" as Path<T>,
"" as PathValue<T, Path<T>>,
);
form.setValue(
"laeSeCd" as Path<T>,
"" as PathValue<T, Path<T>>,
);
form.setValue(
"enoCd" as Path<T>,
"" as PathValue<T, Path<T>>,
);
}}
className="w-[16rem]"
placeholder="기관 선택"
@@ -508,7 +533,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="dtlInstCd"
name={"dtlInstCd" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -516,8 +541,14 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
value={field.value ?? ""}
onValueChange={(value) => {
field.onChange(value);
form.setValue("laeSeCd", "");
form.setValue("enoCd", "");
form.setValue(
"laeSeCd" as Path<T>,
"" as PathValue<T, Path<T>>,
);
form.setValue(
"enoCd" as Path<T>,
"" as PathValue<T, Path<T>>,
);
}}
>
<SelectTrigger className="w-[16rem]">
@@ -544,7 +575,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="laeSeCd"
name={"laeSeCd" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -552,7 +583,10 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
value={field.value ?? ""}
onValueChange={(value) => {
field.onChange(value);
form.setValue("enoCd", "");
form.setValue(
"enoCd" as Path<T>,
"" as PathValue<T, Path<T>>,
);
}}
>
<SelectTrigger className="w-[16rem]">
@@ -579,7 +613,7 @@ function UsersForm({ userId, form, ...props }: UsersFormProps) {
<TableCell className="border">
<FormField
control={form.control}
name="enoCd"
name={"enoCd" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
+18 -22
View File
@@ -1,7 +1,7 @@
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 type { UseFormReturn, Path } from "react-hook-form";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Input,
@@ -35,7 +35,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
form,
handleSave,
}: PostFormProps<T>) {
const atflCd = form.getValues("atflCd" as FieldPath<T>);
const atflCd = form.getValues("atflCd" as Path<T>);
const router = useRouter();
const { confirm } = useConfirm();
@@ -81,7 +81,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<TableCell className="border">
<FormField
control={form.control}
name={"pstTtl" as FieldPath<T>}
name={"pstTtl" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -102,7 +102,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<TableCell className="border">
<FormField
control={form.control}
name={"wrtrNm" as FieldPath<T>}
name={"wrtrNm" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -124,7 +124,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<TableCell className="border">
<FormField
control={form.control}
name={"pstPswd" as FieldPath<T>}
name={"pstPswd" as Path<T>}
render={({ field }) => (
<FormItem className="flex flex-col">
<FormControl>
@@ -147,7 +147,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<TableCell className="border">
<FormField
control={form.control}
name={"pstCn" as FieldPath<T>}
name={"pstCn" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -170,7 +170,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<TableCell className="border">
<FormField
control={form.control}
name={"rfrncUrl" as FieldPath<T>}
name={"rfrncUrl" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -194,7 +194,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<div className="flex items-center gap-4">
<FormField
control={form.control}
name={"popupYn" as FieldPath<T>}
name={"popupYn" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -218,7 +218,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
/>
<FormField
control={form.control}
name={"popupYnEtc" as FieldPath<T>}
name={"popupYnEtc" as Path<T>}
render={({ field }) => {
const fieldValues = form.getValues().popupYnEtc?.[0];
return (
@@ -227,9 +227,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<PostingPeriodSelector
defaultValues={{ ...fieldValues }}
disabled={
form.getValues(
"popupYn" as FieldPath<T>,
) === "N"
form.getValues("popupYn" as Path<T>) === "N"
}
handlePeriodValueChange={(
periodCode,
@@ -267,7 +265,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<div className="flex items-center gap-4">
<FormField
control={form.control}
name={"upndFixYn" as FieldPath<T>}
name={"upndFixYn" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -291,7 +289,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
/>
<FormField
control={form.control}
name={"upndFixYnEtc" as FieldPath<T>}
name={"upndFixYnEtc" as Path<T>}
render={({ field }) => {
const fieldValues =
form.getValues().upndFixYnEtc?.[0];
@@ -301,9 +299,8 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<PostingPeriodSelector
defaultValues={{ ...fieldValues }}
disabled={
form.getValues(
"upndFixYn" as FieldPath<T>,
) === "N"
form.getValues("upndFixYn" as Path<T>) ===
"N"
}
handlePeriodValueChange={(
periodCode,
@@ -341,7 +338,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<div className="flex items-center gap-4">
<FormField
control={form.control}
name={"prdPstgYn" as FieldPath<T>}
name={"prdPstgYn" as Path<T>}
render={({ field }) => (
<FormItem>
<FormControl>
@@ -365,7 +362,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
/>
<FormField
control={form.control}
name={"prdPstgYnEtc" as FieldPath<T>}
name={"prdPstgYnEtc" as Path<T>}
render={({ field }) => {
const fieldValues =
form.getValues().prdPstgYnEtc?.[0];
@@ -375,9 +372,8 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
<PostingPeriodSelector
defaultValues={{ ...fieldValues }}
disabled={
form.getValues(
"prdPstgYn" as FieldPath<T>,
) === "N"
form.getValues("prdPstgYn" as Path<T>) ===
"N"
}
handlePeriodValueChange={(
periodCode,