From 6a4c37603dd07b7cd82a51a14dd26ca7488d5967 Mon Sep 17 00:00:00 2001 From: chan Date: Wed, 25 Mar 2026 16:26:01 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Admin=20UI=20=EC=BB=A4=EC=8A=A4=ED=85=80?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20ID=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EB=AC=B8=EC=A0=9C=20=EB=B0=8F=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=20=EA=B0=9C=EC=84=A0=20(#440)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자 정보 수정(UpdateUser) 시 메타데이터(커스텀 필드)를 명시적 loginId 값보다 우선하여 동기화하도록 로직 순서 변경 - Admin UI 사용자 상세의 비밀번호 초기화 기능이 즉시 폼에 덮어씌워지는 문제 해결을 위해, 별도의 확인 절차 후 즉각 독립적인 API 호출을 통해 재설정되도록 개선 --- .../src/features/users/UserDetailPage.tsx | 66 ++++++++++++++----- backend/internal/handler/user_handler.go | 35 +++++----- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/adminfront/src/features/users/UserDetailPage.tsx b/adminfront/src/features/users/UserDetailPage.tsx index 0f057712..6db94a92 100644 --- a/adminfront/src/features/users/UserDetailPage.tsx +++ b/adminfront/src/features/users/UserDetailPage.tsx @@ -148,7 +148,8 @@ function UserDetailPage() { const queryClient = useQueryClient(); const [error, setError] = React.useState(null); const [successMsg, setSuccessMsg] = React.useState(null); - const [showPassword, setShowPassword] = React.useState(false); + const [isPasswordResetOpen, setIsPasswordResetOpen] = React.useState(false); + const [generatedPassword, setGeneratedPassword] = React.useState(null); const { data: profile } = useQuery({ queryKey: ["me"], @@ -189,7 +190,6 @@ function UserDetailPage() { department: "", position: "", jobTitle: "", - password: "", metadata: {}, }, }); @@ -197,22 +197,38 @@ function UserDetailPage() { const isAdmin = profile?.role === "super_admin" || profile?.role === "tenant_admin"; + const resetPasswordMutation = useMutation({ + mutationFn: (newPass: string) => updateUser(userId, { password: newPass }), + onSuccess: (_, newPass) => { + setGeneratedPassword(newPass); + toast.success( + t( + "msg.admin.users.detail.password_generated", + "사용자 비밀번호가 성공적으로 재설정되었습니다.", + ), + ); + }, + onError: (err: AxiosError<{ error?: string }>) => { + toast.error( + err.response?.data?.error || + t("msg.admin.users.detail.update_error", "수정에 실패했습니다."), + ); + }, + }); + const handleGeneratePassword = () => { + setIsPasswordResetOpen(true); + setGeneratedPassword(null); + }; + + const confirmGeneratePassword = () => { const newPass = generateSecurePassword(); - setValue("password", newPass); - setShowPassword(true); - toast.success( - t( - "msg.admin.users.detail.password_generated", - "안전한 비밀번호가 생성되었습니다.", - ), - ); + resetPasswordMutation.mutate(newPass); }; const handleCopyPassword = () => { - const pass = watch("password"); - if (pass) { - navigator.clipboard.writeText(pass); + if (generatedPassword) { + navigator.clipboard.writeText(generatedPassword); toast.success( t("msg.common.copied_to_clipboard", "클립보드에 복사되었습니다."), ); @@ -670,14 +686,34 @@ function UserDetailPage() { - {showPassword && ( + {isPasswordResetOpen && !generatedPassword && ( +
+

+ {t( + "msg.admin.users.detail.reset_password_confirm", + "정말로 이 사용자의 비밀번호를 초기화하시겠습니까? 기존 비밀번호로는 즉시 로그인할 수 없게 됩니다.", + )} +

+
+ + +
+
+ )} + + {generatedPassword && (

Generated Password

- {watch("password")} + {generatedPassword}