'use client'; import { useActionState, useEffect, useRef, useState } from 'react'; import { FoxButton } from '@fox/core/components/fox-button'; import { FoxFileUpload } from '@fox/core/components/fox-file-upload'; import { FoxFormLabel } from '@fox/core/components/fox-form-label'; import { FoxHelperText } from '@fox/core/components/fox-helper-text'; import { FoxInput } from '@fox/core/components/fox-input'; import { FoxModal } from '@fox/core/components/fox-modal'; import { FoxSelect } from '@fox/core/components/fox-select'; import { FoxToggleSwitch } from '@fox/core/components/fox-toggle-switch'; import { submitFormAction } from '@/app/_hooks/submit-form-action'; import { useFeedback } from '@/app/_hooks/use-feedback'; import { fileModuleAccept, FILE_MODULE } from '@/lib/domain/file-module'; import { formatParentLabel, type SystemMenu, type SystemMenuNode, } from '@/lib/domain/system-menu'; import { DESCRIPTION_MAX_LENGTH, INITIAL_SYSTEM_MENU_FORM_STATE, NAME_MAX_LENGTH, URL_MAX_LENGTH, } from '@/lib/domain/system-menu-form'; import { saveSystemMenuAction } from '../_actions'; import styles from './menu-form.module.scss'; interface MenuFormModalProps { mode: 'create' | 'edit'; /** 수정 대상. 추가일 때는 비어 있다. */ menu?: SystemMenuNode; /** 추가일 때의 상위 메뉴. 수정일 때는 대상의 상위다. */ parent: SystemMenu | null; roleOptions: { value: string; label: string }[]; onClose: () => void; } const ICON_ACCEPT = fileModuleAccept(FILE_MODULE.menuIcon); const ICON_HINT = '이미지 파일만 업로드 가능 (jpg, png, svg 등). 앱 화면에서 메뉴 표시 시 사용됩니다.'; export function MenuFormModal({ mode, menu, parent, roleOptions, onClose, }: MenuFormModalProps) { const isCreate = mode === 'create'; const { showToast } = useFeedback(); const formRef = useRef(null); const [state, formAction, isPending] = useActionState( saveSystemMenuAction, INITIAL_SYSTEM_MENU_FORM_STATE ); const [roleId, setRoleId] = useState(menu?.roleId ?? roleOptions[0]?.value ?? ''); const [isTopActive, setIsTopActive] = useState(menu?.isTopActive ?? false); const [isActive, setIsActive] = useState(menu?.isActive ?? true); // 새창여부는 저장할 컬럼이 없어 화면에만 둔다(사용자 확정). const [opensNewWindow, setOpensNewWindow] = useState(false); const [largeIconId, setLargeIconId] = useState(menu?.largeIconId ?? ''); const [smallIconId, setSmallIconId] = useState(menu?.smallIconId ?? ''); useEffect(() => { if (state.status === 'success') { showToast({ variant: 'success', message: isCreate ? '메뉴를 등록했습니다.' : '메뉴를 수정했습니다.', }); onClose(); } }, [state, showToast, onClose, isCreate]); const errors = state.status === 'error' ? (state.errors ?? {}) : {}; const parentLabel = formatParentLabel(parent); function iconFiles(id: string, name: string | null, url: string | null) { return id ? [{ id, name: name ?? '등록된 아이콘', previewUrl: url ?? undefined }] : []; } return ( formRef.current?.requestSubmit()} /> } >
submitFormAction(event, formAction)} className={styles.fields} > {/* 기획 ① 상위 메뉴가 없으면(최상위 등록) 이 칸을 감춘다. */} {parentLabel && (
)}
최상위권한활성
새창여부 {/* TODO(백엔드): TB_SYS_MENU에 새창여부 컬럼이 없어 저장되지 않는다. */}
setLargeIconId('')} /> {ICON_HINT}
setSmallIconId('')} /> {ICON_HINT}
활성여부
{state.status === 'error' && state.message && ( )}
); }