feat(WEB): 3depth 메뉴 구조 지원 및 CEO 대시보드 개선

- 사이드바 메뉴 3depth 이상 지원 (재귀 컴포넌트)
- menuTransform.ts: buildChildrenRecursive 함수 추가
- AuthenticatedLayout.tsx: findMenuRecursive + ancestorIds 배열로 경로 매칭
- Sidebar.tsx: depth별 스타일 (1depth: 아이콘+굵은텍스트, 2depth: 작은아이콘, 3depth: dot+작은텍스트)
- CEO 대시보드 상세 모달 및 카드 관리 개선
- 폴더블 기기 레이아웃 가이드 문서 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2026-01-09 13:35:05 +09:00
parent c4412295fa
commit f92393f898
8 changed files with 997 additions and 191 deletions

View File

@@ -192,8 +192,30 @@ interface ApiMenu {
external_url: string | null;
}
/**
* 재귀적으로 자식 메뉴를 찾아서 트리 구조로 변환 (3depth 이상 지원)
*/
function buildChildrenRecursive(parentId: number, allMenus: ApiMenu[]): SerializableMenuItem[] {
const children = allMenus
.filter((menu) => menu.parent_id === parentId)
.sort((a, b) => a.sort_order - b.sort_order)
.map((menu) => {
const grandChildren = buildChildrenRecursive(menu.id, allMenus);
return {
id: menu.id.toString(),
label: menu.name,
iconName: menu.icon || 'folder',
path: menu.url || '#',
children: grandChildren.length > 0 ? grandChildren : undefined,
};
});
return children;
}
/**
* API 메뉴 데이터를 SerializableMenuItem 구조로 변환 (localStorage 저장용)
* 3depth 이상의 메뉴 구조 지원
*/
export function transformApiMenusToMenuItems(apiMenus: ApiMenu[]): SerializableMenuItem[] {
if (!apiMenus || !Array.isArray(apiMenus)) {
@@ -201,27 +223,19 @@ export function transformApiMenusToMenuItems(apiMenus: ApiMenu[]): SerializableM
}
// parent_id가 null인 최상위 메뉴만 추출
const parentMenus = apiMenus
const rootMenus = apiMenus
.filter((menu) => menu.parent_id === null)
.sort((a, b) => a.sort_order - b.sort_order);
// 각 부모 메뉴에 대해 자식 메뉴 찾기
const menuItems: SerializableMenuItem[] = parentMenus.map((parentMenu) => {
const children = apiMenus
.filter((menu) => menu.parent_id === parentMenu.id)
.sort((a, b) => a.sort_order - b.sort_order)
.map((childMenu) => ({
id: childMenu.id.toString(),
label: childMenu.name,
iconName: childMenu.icon || 'folder', // 문자열로 저장
path: childMenu.url || '#',
}));
// 각 루트 메뉴에 대해 재귀적으로 자식 메뉴 찾기
const menuItems: SerializableMenuItem[] = rootMenus.map((rootMenu) => {
const children = buildChildrenRecursive(rootMenu.id, apiMenus);
return {
id: parentMenu.id.toString(),
label: parentMenu.name,
iconName: parentMenu.icon || 'folder', // 문자열로 저장
path: parentMenu.url || '#',
id: rootMenu.id.toString(),
label: rootMenu.name,
iconName: rootMenu.icon || 'folder',
path: rootMenu.url || '#',
children: children.length > 0 ? children : undefined,
};
});