File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import 'server-only';
import type { AdminMenu } from '@/lib/domain/admin-menu';
import type { AdminMenuValues } from '@/lib/domain/admin-menu-form';
/**
* 관리자페이지 메뉴 Repository — 기획 ADM_MNU_001/002_p/003_p.
*
* TODO(백엔드): 이 화면의 API가 아직 없다. 아래 mock 저장소를 지우고 다른 Repository처럼
* backendFetch + getSessionAccessToken으로 교체하면 호출부는 그대로 둘 수 있다.
*
* ⚠️ 종전에는 `/api/v1/mngr/menu`(TB_SYS_MENU)를 불렀는데, 그것은 **앱 메뉴**이고
* 「시스템관리 > 메뉴관리」가 이미 같은 표를 편집한다. 두 화면이 같은 행을 고치게 되어
* 이 화면에서 삭제하면 앱 메뉴가 사라졌다. 전용 API가 나오기 전까지 mock으로 끊어 둔다.
*
* 이슈: 저장소가 모듈 메모리라 서버를 다시 띄우면 수정·삭제가 초기값으로 되돌아간다.
*/
const ALL_USERS = { roleId: 'ROLE_USER', roleName: '모든 사용자' };
const STUDENT = { roleId: 'ROLE_STUDENT', roleName: '수강생' };
let MENUS: AdminMenu[] = [
{
id: '14',
parentId: null,
name: 'FOX PAY',
url: '/main',
description: '메인',
sortOrder: 1,
isActive: true,
isTopActive: true,
...ALL_USERS,
},
{
id: '15',
parentId: '14',
name: '서비스 소개',
url: '/intro',
description: '서비스 소개',
sortOrder: 1,
isActive: true,
isTopActive: false,
...ALL_USERS,
},
{
id: '1',
parentId: null,
name: '고객센터',
url: '/cs',
description: '고객센터',
sortOrder: 2,
isActive: true,
isTopActive: true,
...ALL_USERS,
},
{
id: '16',
parentId: '1',
name: '이용 문의',
url: '/cs/inquiry',
description: '1:1 문의',
sortOrder: 1,
isActive: true,
isTopActive: false,
...ALL_USERS,
},
{
id: '4',
parentId: null,
name: '내 강의실',
url: '/class',
description: '내 강의실',
sortOrder: 4,
isActive: true,
isTopActive: true,
...STUDENT,
},
{
id: '5',
parentId: '4',
name: '교육 신청 이력',
url: '/class/history',
description: '교육 신청 이력',
sortOrder: 1,
isActive: true,
isTopActive: false,
...STUDENT,
},
{
id: '9',
parentId: '4',
name: '내 정보 수정',
url: '/class/profile',
description: '내 정보 수정',
sortOrder: 6,
isActive: false,
isTopActive: false,
...STUDENT,
},
];
/** 기획 ADM_MNU_001 ① — 메뉴 등록순 정렬. */
export async function fetchAdminMenus(): Promise<AdminMenu[]> {
return MENUS.map((menu) => ({ ...menu }));
}
function roleNameOf(roleId: string): string {
return (
MENUS.find((menu) => menu.roleId === roleId)?.roleName ?? roleId
);
}
export async function createAdminMenu(
parentId: string,
values: AdminMenuValues
): Promise<void> {
if (!MENUS.some((menu) => menu.id === parentId)) {
throw new Error('상위 메뉴를 찾을 수 없습니다.');
}
// 전체에서 가장 큰 번호 다음 — 형제 안에서만 세면 다른 가지와 번호가 겹친다.
const nextId = MENUS.reduce(
(max, menu) => Math.max(max, Number(menu.id) || 0),
0
) + 1;
MENUS = [
...MENUS,
{
id: String(nextId),
parentId,
name: values.name,
url: values.url,
description: values.description,
sortOrder: values.sortOrder,
roleId: values.roleId,
roleName: roleNameOf(values.roleId),
isActive: values.isActive,
isTopActive: values.isTopActive,
},
];
}
export async function updateAdminMenu(
menuId: string,
values: AdminMenuValues
): Promise<void> {
const index = MENUS.findIndex((menu) => menu.id === menuId);
if (index < 0) {
throw new Error('메뉴를 찾을 수 없습니다.');
}
MENUS[index] = {
...MENUS[index],
name: values.name,
url: values.url,
description: values.description,
sortOrder: values.sortOrder,
roleId: values.roleId,
roleName: roleNameOf(values.roleId),
isActive: values.isActive,
isTopActive: values.isTopActive,
};
}
/** 하위 메뉴까지 함께 지운다 — 부모만 지우면 갈 곳 없는 가지가 남는다. */
export async function deleteAdminMenu(menuId: string): Promise<void> {
if (!MENUS.some((menu) => menu.id === menuId)) {
throw new Error('메뉴를 찾을 수 없습니다.');
}
const doomed = new Set<string>([menuId]);
let grew = true;
while (grew) {
grew = false;
for (const menu of MENUS) {
if (menu.parentId && doomed.has(menu.parentId) && !doomed.has(menu.id)) {
doomed.add(menu.id);
grew = true;
}
}
}
MENUS = MENUS.filter((menu) => !doomed.has(menu.id));
}