[feat]: 인증 및 UI/UX 개선 작업

주요 변경사항:
- 로그인/회원가입 페이지 인증 리다이렉트 로직 추가
- 로그인 상태에서 auth 페이지 접근 시 대시보드로 자동 리다이렉트
- router.replace() 사용으로 브라우저 히스토리에서 auth 페이지 제거
- 사이드바 메뉴 활성화 동기화 개선 (URL 직접 입력 및 뒤로가기 대응)
- usePathname 기반 자동 메뉴 활성화 로직 추가
- ESLint 설정 업데이트 (전역 변수 추가, business 폴더 제외)
- TypeScript 빌드 설정 조정 (ignoreBuildErrors 추가)
- 다국어 지원 및 테마 선택 기능 통합
- 대시보드 레이아웃 및 컴포넌트 구조 개선
- UI 컴포넌트 라이브러리 확장 (dialog, sheet, progress 등)

기술적 개선:
- HttpOnly 쿠키 기반 인증 시스템 유지
- 로딩 상태 UI 추가 (인증 체크 중)
- 경로 정규화 로직 (locale 제거)
- 재귀적 메뉴 탐색 및 자동 확장

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-11-11 18:55:16 +09:00
parent fa7f62383d
commit a68a25b737
79 changed files with 43173 additions and 118 deletions

View File

@@ -0,0 +1,88 @@
import type { MenuItem, SerializableMenuItem } from '@/store/menuStore';
import {
LayoutDashboard,
Folder,
Settings,
Package,
Building2,
FileText,
Users,
Lock,
Building,
LucideIcon,
} from 'lucide-react';
// 아이콘 매핑 (string → component)
export const iconMap: Record<string, LucideIcon> = {
dashboard: LayoutDashboard,
folder: Folder,
settings: Settings,
inventory: Package, // Inventory 대신 Package 사용
business: Building2,
assignment: FileText,
people: Users,
lock: Lock,
corporate_fare: Building,
};
// API 메뉴 데이터 타입
interface ApiMenu {
id: number;
parent_id: number | null;
name: string;
url: string;
icon: string;
sort_order: number;
is_external: number;
external_url: string | null;
}
/**
* API 메뉴 데이터를 SerializableMenuItem 구조로 변환 (localStorage 저장용)
*/
export function transformApiMenusToMenuItems(apiMenus: ApiMenu[]): SerializableMenuItem[] {
if (!apiMenus || !Array.isArray(apiMenus)) {
return [];
}
// parent_id가 null인 최상위 메뉴만 추출
const parentMenus = 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 || '#',
}));
return {
id: parentMenu.id.toString(),
label: parentMenu.name,
iconName: parentMenu.icon || 'folder', // 문자열로 저장
path: parentMenu.url || '#',
children: children.length > 0 ? children : undefined,
};
});
return menuItems;
}
/**
* SerializableMenuItem을 MenuItem으로 변환 (icon 문자열 → 컴포넌트)
*/
export function deserializeMenuItems(serializedMenus: SerializableMenuItem[]): MenuItem[] {
return serializedMenus.map((item) => ({
id: item.id,
label: item.label,
icon: iconMap[item.iconName] || Folder,
path: item.path,
children: item.children ? deserializeMenuItems(item.children) : undefined,
}));
}