'use client'; import { useActionState, useEffect, useRef, useState } from 'react'; import { FoxButton } from '@fox/core/components/fox-button'; import { FoxButtonGroup } from '@fox/core/components/fox-button-group'; import { FoxFormLabel } from '@fox/core/components/fox-form-label'; import { FoxInput } from '@fox/core/components/fox-input'; import { FoxModal } from '@fox/core/components/fox-modal'; import { FoxToggleSwitch } from '@fox/core/components/fox-toggle-switch'; import { foxPasswordValidator } from '@fox/core/validation'; import { FoxChatCenteredDotsIcon } from '@fox/core/icons'; import { useFeedback } from '@/app/_hooks/use-feedback'; import { DEFAULT_ADMIN_ROLE_CODE, type AdminMember } from '@/lib/domain/admin-member'; import { ADMIN_EMAIL_MESSAGES, ADMIN_PASSWORD_ERROR_TEXT, ADMIN_PASSWORD_HELP_TEXT, ADMIN_PASSWORD_POLICY, INITIAL_ADMIN_MEMBER_FORM_STATE, adminEmailValidators, toPhoneDigits, } from '@/lib/domain/admin-member-form'; import { updateAdminMemberAction } from '../_actions'; import { AdminMemberFormFields } from './admin-member-form-fields'; import styles from './admin-member-modal.module.scss'; import { submitFormAction } from '@/app/_hooks/submit-form-action'; interface AdminMemberEditModalProps { member: AdminMember; onClose: () => void; } /** * 관리자 수정 팝업 — 시안: 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) ADM_ADM_103_p (5227:3351) * * 이름·ID는 읽기 전용이다(시안 ①). 화면의 readOnly는 표시일 뿐이지만 Server Action도 두 값을 아예 * 읽지 않고, 백엔드 수정 API 역시 받지 않는다(`MngrAdminUpdateRequestVo`에 필드가 없다) — 세 겹이 * 같은 말을 한다. * * **비밀번호는 [비밀번호 변경]을 눌러야 입력할 수 있고, 비워 두면 바꾸지 않는다**(시안 ②). * 백엔드 UPDATE의 `LOGIN_PW`가 ``로 감싸여 있어 * 빈 값은 SET 절에서 빠진다. (예전에는 조건 없이 덮어써서 빈 값을 보내면 그 계정이 로그인 불가가 * 됐고, 그래서 수정 시 비밀번호를 필수로 막아 두었다 — 그 제약은 이제 없다.) * * 이메일은 시안이 **단일 입력**이다(등록 팝업의 도메인 셀렉트가 없다). 두 시안의 차이를 그대로 * 따른다 — 수정은 이미 있는 주소를 고치는 자리라 도메인만 고르는 형태가 맞지 않는다. * * 휴대전화번호·이메일·역할은 이제 목록 응답에 담겨 오므로 **기존 값으로 채운다**(백엔드가 * `ADM_TEL_NO`·`ADM_EML_ADDR`을 select 목록에 넣었다). * * "잠김여부"는 시안이 활성/비활성 토글이고 백엔드 필드는 `acctLockYn`(잠김 여부)이라 의미가 * 뒤집혀 있다 — 켜짐이 곧 "잠기지 않음"이다. */ export function AdminMemberEditModal({ member, onClose, }: AdminMemberEditModalProps) { const { showToast } = useFeedback(); const [state, formAction, isPending] = useActionState( updateAdminMemberAction, INITIAL_ADMIN_MEMBER_FORM_STATE ); const formRef = useRef(null); const [isPasswordEditable, setIsPasswordEditable] = useState(false); const [phoneNumber, setPhoneNumber] = useState( toPhoneDigits(member.phoneNumber) ); const [email, setEmail] = useState(member.email ?? ''); const [roleCode, setRoleCode] = useState( member.roleCode || DEFAULT_ADMIN_ROLE_CODE ); const [menuCodes, setMenuCodes] = useState(member.menuCodes); const [isActive, setIsActive] = useState(!(member.isLocked ?? false)); useEffect(() => { if (state.status === 'success') { showToast({ variant: 'success', message: '관리자 정보를 수정했습니다.' }); onClose(); } }, [state, showToast, onClose]); const errors = state.status === 'error' ? (state.errors ?? {}) : {}; return ( formRef.current?.requestSubmit()} /> } >
submitFormAction(event, formAction)} className={styles.formList}> {/* 수정 대상을 가리키는 유일한 입력. 이름·ID는 읽기 전용이라 아예 제출하지 않는다. */}
: undefined } invalid={Boolean(errors.password)} // 비우면 "바꾸지 않음"이라 required를 걸지 않는다(등록 팝업과 다른 점). validators={[foxPasswordValidator(ADMIN_PASSWORD_POLICY)]} validationMessages={{ minlength: ADMIN_PASSWORD_ERROR_TEXT }} /> setIsPasswordEditable(true)} />
} />
잠김여부
{/* 화면은 활성(잠기지 않음)을 보여주고 서버는 잠김 여부를 받는다 — 여기서 뒤집는다. */} {state.status === 'error' && state.message && (

{state.message}

)}
); }