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 } from 'react';
import { FoxButton } from '@fox/core/components/fox-button';
import {
FoxDescriptionList,
type FoxDescriptionItem,
} from '@fox/core/components/fox-description-list';
import { FoxModal } from '@fox/core/components/fox-modal';
import { FoxToggleSwitch } from '@fox/core/components/fox-toggle-switch';
import {
formatGradeClassNumber,
formatOptionalValue,
type StudentMember,
} from '@/lib/domain/student-member';
import {
updateStudentActiveStatus,
type UpdateStudentActiveStatusState,
} from '../_actions';
interface StudentDetailModalProps {
member: StudentMember;
onClose: () => void;
}
const INITIAL_STATE: UpdateStudentActiveStatusState = { status: 'idle' };
const DETAIL_FORM_ID = 'student-active-status-form';
/**
* 사용여부 수정 가능 여부의 단일 토글. 백엔드에 사용여부 값도 변경 API도 없어 현재는 false다 —
* 변경 API가 생기면 이 상수를 true로 되돌리고 `_actions.ts`의 미지원 처리를 실제 구현으로
* 바꾸면 된다(토글·저장 버튼의 비활성이 함께 풀린다).
*/
const IS_ACTIVE_STATUS_EDITABLE = false;
const ACTIVE_STATUS_UNSUPPORTED_NOTE =
'사용여부 변경은 백엔드 API 준비 후 제공됩니다.';
/** 시안은 두 값을 " / "로 묶는다("홍라애 / 010-2222-1234"). 둘 다 없으면 `-`다. */
function joinPair(first: string | null, second: string | null) {
const parts = [first, second].filter(
(value): value is string => value !== null && value !== ''
);
return parts.length > 0 ? parts.join(' / ') : '-';
}
/**
* 학생 회원 조회 팝업 — 시안: 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) modal 5227:809
*
* 모든 항목이 조회 전용이다. 데이터는 목록 행이 이미 갖고 있는 member를 그대로 받으므로
* 재조회하지 않는다(백엔드에 단건 조회 API도 아직 없다). 백엔드가 주지 않는 값은 `-`다.
*
* 시안은 보호자(이름/연락처)와 학교(학교/학년·반·번호)를 각각 한 줄로 묶는다 — 종전처럼
* 항목을 따로 두지 않고 시안의 8줄 구성을 그대로 따랐다.
*
* 사용여부는 이 팝업에서 유일하게 수정 가능한 항목이지만, 백엔드 목록 응답에 값이 없고 변경
* API도 없어 토글·저장 버튼을 비활성으로 둔다(`_actions.ts`의 미지원 처리와 짝). 폼·Server
* Action 배선은 남겨 두었으므로 API가 생기면 `IS_ACTIVE_STATUS_EDITABLE`만 되돌리면 된다.
*
* 저장 버튼은 `form={DETAIL_FORM_ID}`로 폼과 이어 둔다 — FoxModal이 버튼을 `foot` 슬롯에
* 그려 폼의 자손이 아니게 되는데, 네이티브 `form` 속성은 같은 문서 안에서 id만 맞으면 그
* 폼을 제출한다.
*/
export function StudentDetailModal({ member, onClose }: StudentDetailModalProps) {
const [state, formAction, isPending] = useActionState(
updateStudentActiveStatus,
INITIAL_STATE
);
const formRef = useRef<HTMLFormElement>(null);
useEffect(() => {
if (state.status === 'success') {
onClose();
}
}, [state, onClose]);
const items: FoxDescriptionItem[] = [
{ key: 'name', term: '이름', description: member.name },
{ key: 'loginId', term: 'ID', description: member.loginId },
{
key: 'phoneNumber',
term: '휴대전화 번호',
description: formatOptionalValue(member.phoneNumber),
},
{
key: 'email',
term: '이메일',
description: formatOptionalValue(member.email),
},
{
key: 'birthDate',
term: '생년월일',
description: formatOptionalValue(member.birthDate),
},
{
key: 'guardian',
term: '보호자 이름 / 연락처',
description: joinPair(member.guardianName, member.guardianPhoneNumber),
},
{
key: 'school',
term: '학교 / 학년·반·번호',
description: joinPair(
member.schoolName,
formatGradeClassNumber(member) === '-'
? null
: formatGradeClassNumber(member)
),
},
{
key: 'isActive',
term: '사용여부',
description: (
<FoxToggleSwitch
name="isActive"
value="true"
label={member.isActive ? '활성' : '비활성'}
defaultChecked={member.isActive ?? false}
disabled={!IS_ACTIVE_STATUS_EDITABLE}
/>
),
},
];
return (
<FoxModal
open
size="sm"
title="학생 회원 조회"
onClose={onClose}
actions={
<>
<FoxButton
type="default"
size="md"
label="취소"
onAction={onClose}
/>
{/* FoxButton에는 폼을 가리키는 prop이 없다(네이티브 `form` 속성 미노출). 버튼이
foot 슬롯에 그려져 폼의 자손이 아니므로 ref로 직접 제출한다.
비활성 사유는 감싼 span의 title로 남긴다 — 비활성 버튼은 hover를 받지 못한다. */}
<span
title={
IS_ACTIVE_STATUS_EDITABLE ? undefined : ACTIVE_STATUS_UNSUPPORTED_NOTE
}
>
<FoxButton
type="primary"
size="md"
label={isPending ? '저장 중...' : '저장'}
disabled={isPending || !IS_ACTIVE_STATUS_EDITABLE}
onAction={() => formRef.current?.requestSubmit()}
/>
</span>
</>
}
>
<form ref={formRef} id={DETAIL_FORM_ID} action={formAction}>
<input type="hidden" name="id" value={member.id} />
<FoxDescriptionList items={items} />
</form>
</FoxModal>
);
}