File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'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 { 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_PASSWORD_HELP_TEXT,
INITIAL_ADMIN_MEMBER_FORM_STATE,
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';
interface AdminMemberEditModalProps {
member: AdminMember;
onClose: () => void;
}
/**
* 관리자 수정 팝업 — 시안: 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) ADM_ADM_103_p (5227:3351)
*
* 이름·ID는 읽기 전용이다(시안 ①). 화면의 readOnly는 표시일 뿐이지만 Server Action도 두 값을 아예
* 읽지 않고, 백엔드 수정 API 역시 받지 않는다(`MngrAdminUpdateRequestVo`에 필드가 없다) — 세 겹이
* 같은 말을 한다.
*
* **비밀번호는 [비밀번호 변경]을 눌러야 입력할 수 있고, 비워 두면 바꾸지 않는다**(시안 ②).
* 백엔드 UPDATE의 `LOGIN_PW`가 `<if test='loginPw != null and loginPw != ""'>`로 감싸여 있어
* 빈 값은 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<HTMLFormElement>(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<string[]>(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 (
<FoxModal
open
onClose={onClose}
size="sm"
title="관리자 수정"
actions={
<FoxButtonGroup size="md">
<FoxButton type="default" size="md" label="취소" onAction={onClose} />
<FoxButton
type="primary"
size="md"
label="저장"
loading={isPending}
onAction={() => formRef.current?.requestSubmit()}
/>
</FoxButtonGroup>
}
>
<form ref={formRef} action={formAction} className={styles.formList}>
{/* 수정 대상을 가리키는 유일한 입력. 이름·ID는 읽기 전용이라 아예 제출하지 않는다. */}
<input type="hidden" name="id" value={member.id} />
<FoxInput
size="md"
label="이름"
requirement="required"
value={member.name}
readOnly
/>
<FoxInput
size="md"
label="ID"
requirement="required"
value={member.loginId}
readOnly
/>
<div className={styles.fieldWithAction}>
<FoxInput
size="md"
type="password"
name="password"
label="비밀번호"
requirement="required"
placeholder="비밀번호를 입력하세요."
autoComplete="new-password"
disabled={!isPasswordEditable}
message={
errors.password ??
(isPasswordEditable ? ADMIN_PASSWORD_HELP_TEXT : undefined)
}
messageIcon={
isPasswordEditable ? <FoxChatCenteredDotsIcon /> : undefined
}
invalid={Boolean(errors.password)}
/>
<FoxButton
type="secondary"
size="md"
fullWidth
label="비밀번호 변경"
disabled={isPasswordEditable}
onAction={() => setIsPasswordEditable(true)}
/>
</div>
<AdminMemberFormFields
phoneNumber={phoneNumber}
onPhoneNumberChange={setPhoneNumber}
roleCode={roleCode}
onRoleCodeChange={setRoleCode}
menuCodes={menuCodes}
onMenuCodesChange={setMenuCodes}
errors={errors}
emailField={
<FoxInput
size="md"
type="email"
name="email"
label="이메일"
placeholder="이메일을 입력하세요."
value={email}
onChange={setEmail}
message={errors.email}
invalid={Boolean(errors.email)}
/>
}
/>
<div className={styles.field}>
<FoxFormLabel as="span">잠김여부</FoxFormLabel>
<FoxToggleSwitch
size="md"
label={isActive ? '활성' : '비활성'}
checked={isActive}
onChange={setIsActive}
/>
</div>
{/* 화면은 활성(잠기지 않음)을 보여주고 서버는 잠김 여부를 받는다 — 여기서 뒤집는다. */}
<input type="hidden" name="isLocked" value={String(!isActive)} />
{state.status === 'error' && state.message && (
<p role="alert">{state.message}</p>
)}
</form>
</FoxModal>
);
}