어드민 메뉴 관리 목록을 계층형으로 개선
This commit is contained in:
@@ -44,8 +44,31 @@ export const getAdminMenuList = async (pageNum: number = 1): Promise<AdminMenuPa
|
||||
return response.data.resultData || { content: [], pageNum: 1, pageSize: 0, totalContent: 0, totalPage: 0, isFirstPage: true, isLastPage: true };
|
||||
};
|
||||
|
||||
// parentSeq로 어드민 메뉴 목록 조회
|
||||
export const getAdminMenuListByParentSeq = async (parentSeq: number): Promise<AdminMenu[]> => {
|
||||
// parentSeq로 어드민 메뉴 목록 조회 (페이징 지원)
|
||||
export const getAdminMenuListByParentSeq = async (parentSeq: number, pageNum: number = 1): Promise<AdminMenuPageResponse> => {
|
||||
const response = await axios.get<AdminMenuResponse>(`/admin/menu/listByParentSeq/${parentSeq}?pageNum=${pageNum}`);
|
||||
const resultData = response.data.resultData;
|
||||
// 페이징 형태의 응답인 경우 그대로 반환
|
||||
if (resultData && resultData.content && Array.isArray(resultData.content)) {
|
||||
return resultData;
|
||||
}
|
||||
// 배열 형태의 응답인 경우 페이징 형태로 변환
|
||||
if (Array.isArray(resultData)) {
|
||||
return {
|
||||
content: resultData,
|
||||
pageNum: 1,
|
||||
pageSize: resultData.length,
|
||||
totalContent: resultData.length,
|
||||
totalPage: 1,
|
||||
isFirstPage: true,
|
||||
isLastPage: true,
|
||||
};
|
||||
}
|
||||
return { content: [], pageNum: 1, pageSize: 0, totalContent: 0, totalPage: 0, isFirstPage: true, isLastPage: true };
|
||||
};
|
||||
|
||||
// parentSeq로 어드민 메뉴 목록 조회 (페이징 없이 전체 목록)
|
||||
export const getAdminMenuListByParentSeqAll = async (parentSeq: number): Promise<AdminMenu[]> => {
|
||||
const response = await axios.get<AdminMenuResponse>(`/admin/menu/listByParentSeq/${parentSeq}`);
|
||||
const resultData = response.data.resultData;
|
||||
// 페이징 형태의 응답인 경우 content 배열 반환
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
CButton,
|
||||
CCard,
|
||||
CCardBody,
|
||||
CCardHeader,
|
||||
CCol,
|
||||
CForm,
|
||||
CFormInput,
|
||||
CFormLabel,
|
||||
@@ -15,14 +15,12 @@ import {
|
||||
CModalFooter,
|
||||
CModalHeader,
|
||||
CModalTitle,
|
||||
CRow,
|
||||
CTable,
|
||||
CTableBody,
|
||||
CTableDataCell,
|
||||
CTableHead,
|
||||
CTableHeaderCell,
|
||||
CTableRow,
|
||||
CBadge,
|
||||
CFormSelect,
|
||||
CPagination,
|
||||
CPaginationItem,
|
||||
@@ -135,8 +133,8 @@ const availableIcons = [
|
||||
];
|
||||
|
||||
import {
|
||||
getAdminMenuList,
|
||||
getAdminMenuListByParentSeq,
|
||||
getAdminMenu,
|
||||
addAdminMenu,
|
||||
updateAdminMenu,
|
||||
deleteAdminMenu,
|
||||
@@ -144,6 +142,12 @@ import {
|
||||
AdminMenuPageResponse,
|
||||
} from 'src/services/adminMenuService';
|
||||
|
||||
// 브레드크럼 아이템 인터페이스
|
||||
interface BreadcrumbItem {
|
||||
adminMenuSeq: number;
|
||||
menuName: string;
|
||||
}
|
||||
|
||||
// 아이콘 이름으로 아이콘 객체 찾기
|
||||
const getIconByName = (name: string) => {
|
||||
const found = availableIcons.find((item) => item.name === name);
|
||||
@@ -151,8 +155,8 @@ const getIconByName = (name: string) => {
|
||||
};
|
||||
|
||||
const AdminMenuManagement: React.FC = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [menuList, setMenuList] = useState<AdminMenu[]>([]);
|
||||
const [topLevelMenuList, setTopLevelMenuList] = useState<AdminMenu[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
||||
@@ -167,6 +171,11 @@ const AdminMenuManagement: React.FC = () => {
|
||||
menuUrl: '',
|
||||
});
|
||||
|
||||
// 현재 보고 있는 부모 메뉴 seq (0이면 최상위)
|
||||
const [currentParentSeq, setCurrentParentSeq] = useState(0);
|
||||
// 브레드크럼 (탐색 경로)
|
||||
const [breadcrumb, setBreadcrumb] = useState<BreadcrumbItem[]>([]);
|
||||
|
||||
// 페이징 상태
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageInfo, setPageInfo] = useState<Omit<AdminMenuPageResponse, 'content'>>({
|
||||
@@ -192,12 +201,24 @@ const AdminMenuManagement: React.FC = () => {
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [handleResize]);
|
||||
|
||||
// 메뉴 목록 조회
|
||||
const fetchMenuList = async (page: number = 1) => {
|
||||
// URL 파라미터를 업데이트하는 함수
|
||||
const updateUrlParams = useCallback((parentSeq: number, pageNum: number) => {
|
||||
const params = new URLSearchParams();
|
||||
if (parentSeq > 0) {
|
||||
params.set('parentSeq', String(parentSeq));
|
||||
}
|
||||
if (pageNum > 1) {
|
||||
params.set('pageNum', String(pageNum));
|
||||
}
|
||||
setSearchParams(params, { replace: false });
|
||||
}, [setSearchParams]);
|
||||
|
||||
// 메뉴 목록 조회 (parentSeq 기준)
|
||||
const fetchMenuList = async (parentSeq: number = 0, pageNum: number = 1) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await getAdminMenuList(page);
|
||||
setMenuList(data.content);
|
||||
const data = await getAdminMenuListByParentSeq(parentSeq, pageNum);
|
||||
setMenuList(data.content || []);
|
||||
setPageInfo({
|
||||
pageNum: data.pageNum,
|
||||
pageSize: data.pageSize,
|
||||
@@ -207,6 +228,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
isLastPage: data.isLastPage,
|
||||
});
|
||||
setCurrentPage(data.pageNum);
|
||||
setCurrentParentSeq(parentSeq);
|
||||
} catch (error) {
|
||||
console.error('메뉴 목록 조회 실패:', error);
|
||||
alert('메뉴 목록을 불러오는데 실패했습니다.');
|
||||
@@ -215,48 +237,105 @@ const AdminMenuManagement: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 최상위 메뉴 목록 조회 (parentSeq가 0인 메뉴들)
|
||||
const fetchTopLevelMenuList = async () => {
|
||||
try {
|
||||
const data = await getAdminMenuListByParentSeq(0);
|
||||
setTopLevelMenuList(data || []);
|
||||
} catch (error) {
|
||||
console.error('최상위 메뉴 목록 조회 실패:', error);
|
||||
setTopLevelMenuList([]);
|
||||
// 메뉴 클릭 시 하위 메뉴로 이동
|
||||
const handleMenuClick = (menu: AdminMenu) => {
|
||||
if (menu.adminMenuSeq) {
|
||||
const newBreadcrumb = [...breadcrumb, { adminMenuSeq: menu.adminMenuSeq!, menuName: menu.menuName }];
|
||||
setBreadcrumb(newBreadcrumb);
|
||||
updateUrlParams(menu.adminMenuSeq, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 브레드크럼 클릭 시 해당 위치로 이동
|
||||
const handleBreadcrumbClick = (index: number) => {
|
||||
if (index === -1) {
|
||||
// 최상위로 이동
|
||||
setBreadcrumb([]);
|
||||
updateUrlParams(0, 1);
|
||||
} else {
|
||||
// 특정 위치로 이동
|
||||
const targetItem = breadcrumb[index];
|
||||
const newBreadcrumb = breadcrumb.slice(0, index + 1);
|
||||
setBreadcrumb(newBreadcrumb);
|
||||
updateUrlParams(targetItem.adminMenuSeq, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 페이지 이동 함수 (URL 업데이트 포함)
|
||||
const handlePageChange = (pageNum: number) => {
|
||||
updateUrlParams(currentParentSeq, pageNum);
|
||||
};
|
||||
|
||||
// 브레드크럼 경로를 API로 복원하는 함수
|
||||
const buildBreadcrumbPath = async (targetSeq: number): Promise<BreadcrumbItem[]> => {
|
||||
const path: BreadcrumbItem[] = [];
|
||||
let currentSeq = targetSeq;
|
||||
|
||||
while (currentSeq > 0) {
|
||||
try {
|
||||
const menu = await getAdminMenu(currentSeq);
|
||||
path.unshift({ adminMenuSeq: menu.adminMenuSeq!, menuName: menu.menuName });
|
||||
currentSeq = menu.parentSeq;
|
||||
} catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
// URL 파라미터 변경 감지 및 초기 로드
|
||||
useEffect(() => {
|
||||
fetchMenuList();
|
||||
}, []);
|
||||
const parentSeqParam = searchParams.get('parentSeq');
|
||||
const pageNumParam = searchParams.get('pageNum');
|
||||
|
||||
const parentSeq = parentSeqParam ? parseInt(parentSeqParam) : 0;
|
||||
const pageNum = pageNumParam ? parseInt(pageNumParam) : 1;
|
||||
|
||||
const loadData = async () => {
|
||||
// 브레드크럼 복원
|
||||
const currentBreadcrumbParent = breadcrumb.length > 0 ? breadcrumb[breadcrumb.length - 1].adminMenuSeq : 0;
|
||||
|
||||
if (parentSeq !== currentBreadcrumbParent) {
|
||||
if (parentSeq === 0) {
|
||||
setBreadcrumb([]);
|
||||
} else {
|
||||
// 브레드크럼에서 해당 parentSeq를 찾아서 그 위치까지만 유지
|
||||
const foundIndex = breadcrumb.findIndex(item => item.adminMenuSeq === parentSeq);
|
||||
if (foundIndex >= 0) {
|
||||
setBreadcrumb(breadcrumb.slice(0, foundIndex + 1));
|
||||
} else {
|
||||
// 브레드크럼에 없으면 API로 경로 복원 (새로고침, 직접 URL 접근 등)
|
||||
const newPath = await buildBreadcrumbPath(parentSeq);
|
||||
setBreadcrumb(newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentParentSeq(parentSeq);
|
||||
fetchMenuList(parentSeq, pageNum);
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [searchParams]);
|
||||
|
||||
// 메뉴 추가 모달 열기
|
||||
const handleAddClick = async () => {
|
||||
const handleAddClick = () => {
|
||||
setIsEditMode(false);
|
||||
setFormData({
|
||||
parentSeq: 0,
|
||||
parentSeq: currentParentSeq,
|
||||
menuOrder: 1,
|
||||
menuName: '',
|
||||
iconName: '',
|
||||
menuUrl: '',
|
||||
});
|
||||
try {
|
||||
await fetchTopLevelMenuList();
|
||||
} catch (error) {
|
||||
console.error('최상위 메뉴 목록 조회 실패:', error);
|
||||
}
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 메뉴 수정 모달 열기
|
||||
const handleEditClick = async (menu: AdminMenu) => {
|
||||
const handleEditClick = (menu: AdminMenu) => {
|
||||
setIsEditMode(true);
|
||||
setFormData({ ...menu });
|
||||
try {
|
||||
await fetchTopLevelMenuList();
|
||||
} catch (error) {
|
||||
console.error('최상위 메뉴 목록 조회 실패:', error);
|
||||
}
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
@@ -309,7 +388,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
}
|
||||
|
||||
setModalVisible(false);
|
||||
fetchMenuList(currentPage);
|
||||
fetchMenuList(currentParentSeq, currentPage);
|
||||
} catch (error) {
|
||||
console.error('메뉴 저장 실패:', error);
|
||||
alert('메뉴 저장에 실패했습니다.');
|
||||
@@ -324,20 +403,13 @@ const AdminMenuManagement: React.FC = () => {
|
||||
await deleteAdminMenu(selectedMenu.adminMenuSeq);
|
||||
alert('메뉴가 삭제되었습니다.');
|
||||
setDeleteModalVisible(false);
|
||||
fetchMenuList(currentPage);
|
||||
fetchMenuList(currentParentSeq, currentPage);
|
||||
} catch (error) {
|
||||
console.error('메뉴 삭제 실패:', error);
|
||||
alert('메뉴 삭제에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// 메뉴의 depth 계산 (계층 구조 표시용)
|
||||
const getMenuDepth = (menu: AdminMenu): number => {
|
||||
if (menu.parentSeq === 0) return 0;
|
||||
const parent = menuList.find((m) => m.adminMenuSeq === menu.parentSeq);
|
||||
return parent ? getMenuDepth(parent) + 1 : 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CCard className="mb-4">
|
||||
@@ -354,39 +426,73 @@ const AdminMenuManagement: React.FC = () => {
|
||||
</CButton>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CTable align="middle" className="mb-0 border" hover responsive style={{ tableLayout: 'fixed', minWidth: isCompact ? '450px' : '750px' }}>
|
||||
{/* 브레드크럼 네비게이션 */}
|
||||
<nav aria-label="breadcrumb" className="mb-3">
|
||||
<ol className="breadcrumb mb-0">
|
||||
<li className={`breadcrumb-item ${breadcrumb.length === 0 ? 'active' : ''}`}>
|
||||
{breadcrumb.length === 0 ? (
|
||||
<span><CIcon icon={cilHome} className="me-1" />최상위 메뉴</span>
|
||||
) : (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => { e.preventDefault(); handleBreadcrumbClick(-1); }}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<CIcon icon={cilHome} className="me-1" />최상위 메뉴
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
{breadcrumb.map((item, index) => (
|
||||
<li
|
||||
key={item.adminMenuSeq}
|
||||
className={`breadcrumb-item ${index === breadcrumb.length - 1 ? 'active' : ''}`}
|
||||
>
|
||||
{index === breadcrumb.length - 1 ? (
|
||||
<span>{item.menuName}</span>
|
||||
) : (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => { e.preventDefault(); handleBreadcrumbClick(index); }}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
{item.menuName}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<CTable align="middle" className="mb-0 border" hover responsive style={{ tableLayout: 'fixed', minWidth: isCompact ? '400px' : '650px' }}>
|
||||
<CTableHead className="text-nowrap">
|
||||
<CTableRow>
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '80px' }}>메뉴 번호</CTableHeaderCell>
|
||||
{!isCompact && (
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '60px' }}>아이콘</CTableHeaderCell>
|
||||
)}
|
||||
<CTableHeaderCell className="bg-body-tertiary" style={{ width: 'auto' }}>메뉴 이름</CTableHeaderCell>
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '80px' }}>부모 번호</CTableHeaderCell>
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '70px' }}>정렬 순서</CTableHeaderCell>
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '180px' }}>메뉴 이름</CTableHeaderCell>
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '70px' }}>순번</CTableHeaderCell>
|
||||
{!isCompact && (
|
||||
<CTableHeaderCell className="bg-body-tertiary" style={{ width: '180px' }}>URL</CTableHeaderCell>
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: 'auto' }}>URL</CTableHeaderCell>
|
||||
)}
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '100px' }}>액션</CTableHeaderCell>
|
||||
<CTableHeaderCell className="bg-body-tertiary text-center" style={{ width: '100px' }}>작업</CTableHeaderCell>
|
||||
</CTableRow>
|
||||
</CTableHead>
|
||||
<CTableBody style={{ minHeight: '200px' }}>
|
||||
{loading ? (
|
||||
<CTableRow>
|
||||
<CTableDataCell colSpan={isCompact ? 5 : 7} className="text-center py-5">
|
||||
<CTableDataCell colSpan={isCompact ? 4 : 6} className="text-center py-5">
|
||||
로딩 중...
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
) : menuList.length === 0 ? (
|
||||
<CTableRow>
|
||||
<CTableDataCell colSpan={isCompact ? 5 : 7} className="text-center">
|
||||
<CTableDataCell colSpan={isCompact ? 4 : 6} className="text-center">
|
||||
등록된 메뉴가 없습니다.
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
) : (
|
||||
menuList.map((menu) => {
|
||||
const depth = getMenuDepth(menu);
|
||||
return (
|
||||
menuList.map((menu) => (
|
||||
<CTableRow key={menu.adminMenuSeq}>
|
||||
<CTableDataCell className="text-center">
|
||||
<div className="fw-semibold">{menu.adminMenuSeq}</div>
|
||||
@@ -400,30 +506,28 @@ const AdminMenuManagement: React.FC = () => {
|
||||
)}
|
||||
</CTableDataCell>
|
||||
)}
|
||||
<CTableDataCell>
|
||||
<CTableDataCell className="text-center">
|
||||
<div
|
||||
style={{
|
||||
marginLeft: `${depth * 20}px`,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
title={menu.menuName}
|
||||
title={`${menu.menuName} - 클릭하여 하위 메뉴 보기`}
|
||||
>
|
||||
{depth > 0 && '└ '}
|
||||
{menu.menuName}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => { e.preventDefault(); handleMenuClick(menu); }}
|
||||
style={{ textDecoration: 'none', color: 'inherit' }}
|
||||
className="fw-semibold"
|
||||
>
|
||||
{menu.menuName}
|
||||
</a>
|
||||
</div>
|
||||
</CTableDataCell>
|
||||
<CTableDataCell className="text-center">
|
||||
{menu.parentSeq === 0 ? (
|
||||
<CBadge color="info" size="sm">최상위</CBadge>
|
||||
) : (
|
||||
menu.parentSeq
|
||||
)}
|
||||
</CTableDataCell>
|
||||
<CTableDataCell className="text-center">{menu.menuOrder}</CTableDataCell>
|
||||
{!isCompact && (
|
||||
<CTableDataCell>
|
||||
<CTableDataCell className="text-center">
|
||||
<div
|
||||
className="small text-body-secondary"
|
||||
style={{
|
||||
@@ -443,6 +547,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
size="sm"
|
||||
className="me-2"
|
||||
onClick={() => handleEditClick(menu)}
|
||||
title="수정"
|
||||
>
|
||||
<CIcon icon={cilPencil} />
|
||||
</CButton>
|
||||
@@ -450,13 +555,13 @@ const AdminMenuManagement: React.FC = () => {
|
||||
color="danger"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteClick(menu)}
|
||||
title="삭제"
|
||||
>
|
||||
<CIcon icon={cilTrash} />
|
||||
</CButton>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
);
|
||||
})
|
||||
))
|
||||
)}
|
||||
</CTableBody>
|
||||
</CTable>
|
||||
@@ -472,7 +577,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
<CPaginationItem
|
||||
aria-label="First"
|
||||
disabled={pageInfo.isFirstPage}
|
||||
onClick={() => fetchMenuList(1)}
|
||||
onClick={() => handlePageChange(1)}
|
||||
style={{ cursor: pageInfo.isFirstPage ? 'default' : 'pointer' }}
|
||||
>
|
||||
«
|
||||
@@ -480,7 +585,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
<CPaginationItem
|
||||
aria-label="Previous"
|
||||
disabled={pageInfo.isFirstPage}
|
||||
onClick={() => fetchMenuList(currentPage - 1)}
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
style={{ cursor: pageInfo.isFirstPage ? 'default' : 'pointer' }}
|
||||
>
|
||||
‹
|
||||
@@ -489,7 +594,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
<CPaginationItem
|
||||
key={page}
|
||||
active={page === currentPage}
|
||||
onClick={() => fetchMenuList(page)}
|
||||
onClick={() => handlePageChange(page)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{page}
|
||||
@@ -498,7 +603,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
<CPaginationItem
|
||||
aria-label="Next"
|
||||
disabled={pageInfo.isLastPage}
|
||||
onClick={() => fetchMenuList(currentPage + 1)}
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
style={{ cursor: pageInfo.isLastPage ? 'default' : 'pointer' }}
|
||||
>
|
||||
›
|
||||
@@ -506,7 +611,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
<CPaginationItem
|
||||
aria-label="Last"
|
||||
disabled={pageInfo.isLastPage}
|
||||
onClick={() => fetchMenuList(pageInfo.totalPage)}
|
||||
onClick={() => handlePageChange(pageInfo.totalPage)}
|
||||
style={{ cursor: pageInfo.isLastPage ? 'default' : 'pointer' }}
|
||||
>
|
||||
»
|
||||
@@ -536,31 +641,20 @@ const AdminMenuManagement: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<CFormLabel htmlFor="parentSeq">부모 메뉴</CFormLabel>
|
||||
<CFormSelect
|
||||
id="parentSeq"
|
||||
name="parentSeq"
|
||||
value={String(formData.parentSeq)}
|
||||
onChange={(e) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
parentSeq: parseInt(e.target.value) || 0,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<option value="0">최상위 메뉴 (부모 없음)</option>
|
||||
{Array.isArray(topLevelMenuList) && topLevelMenuList
|
||||
.filter((menu) => menu.adminMenuSeq !== formData.adminMenuSeq)
|
||||
.map((menu) => (
|
||||
<option key={menu.adminMenuSeq} value={String(menu.adminMenuSeq)}>
|
||||
{menu.menuName} (번호: {menu.adminMenuSeq})
|
||||
</option>
|
||||
))}
|
||||
</CFormSelect>
|
||||
<small className="text-muted">최상위 메뉴를 선택하면 하위 메뉴로 등록됩니다.</small>
|
||||
<CFormLabel>부모 메뉴</CFormLabel>
|
||||
<CFormInput
|
||||
type="text"
|
||||
value={
|
||||
formData.parentSeq === 0
|
||||
? '최상위 메뉴'
|
||||
: breadcrumb.find(item => item.adminMenuSeq === formData.parentSeq)?.menuName || `메뉴 번호: ${formData.parentSeq}`
|
||||
}
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<CFormLabel htmlFor="menuOrder">정렬 순서 *</CFormLabel>
|
||||
<CFormLabel htmlFor="menuOrder">순번 *</CFormLabel>
|
||||
<CFormInput
|
||||
type="number"
|
||||
id="menuOrder"
|
||||
@@ -574,7 +668,7 @@ const AdminMenuManagement: React.FC = () => {
|
||||
menuOrder: value < 1 ? 1 : value,
|
||||
}));
|
||||
}}
|
||||
placeholder="정렬 순서 (1 이상)"
|
||||
placeholder="순번 (1 이상)"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
|
||||
Reference in New Issue
Block a user