UI/UX 개선: 사이드바 레이아웃 안정화 및 메뉴 hover 효과 개선
- 사이드바 고정 너비 설정으로 메뉴 클릭 시 너비 변화 방지 - 메뉴 항목 hover 효과 일관성 개선 (고정 높이, 부드러운 색상 전환) - 디버깅 로그 제거로 성능 최적화 - 관리자 페이지 카드 디자인 개선 (그라데이션 배경, 아이콘 색상 조정)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, Suspense } from "react";
|
||||
import { useState, Suspense, useEffect } from "react";
|
||||
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -197,8 +197,27 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const { user, logout, refreshUserData } = useAuth();
|
||||
const { userMenus, adminMenus, loading, refreshMenus } = useMenu();
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||
const [expandedMenus, setExpandedMenus] = useState<Set<string>>(new Set());
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
// 화면 크기 감지 및 사이드바 초기 상태 설정
|
||||
useEffect(() => {
|
||||
const checkIsMobile = () => {
|
||||
const mobile = window.innerWidth < 1024; // lg 브레이크포인트
|
||||
setIsMobile(mobile);
|
||||
// 모바일에서만 사이드바를 닫음
|
||||
if (mobile) {
|
||||
setSidebarOpen(false);
|
||||
} else {
|
||||
setSidebarOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
checkIsMobile();
|
||||
window.addEventListener('resize', checkIsMobile);
|
||||
return () => window.removeEventListener('resize', checkIsMobile);
|
||||
}, []);
|
||||
|
||||
// 프로필 관련 로직
|
||||
const {
|
||||
@@ -253,15 +272,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
? `/screens/${firstScreen.screenId}?mode=admin`
|
||||
: `/screens/${firstScreen.screenId}`;
|
||||
|
||||
console.log("🎯 메뉴에서 화면으로 이동:", {
|
||||
menuName: menu.name,
|
||||
screenId: firstScreen.screenId,
|
||||
isAdminMode,
|
||||
targetPath: screenPath,
|
||||
});
|
||||
|
||||
router.push(screenPath);
|
||||
setSidebarOpen(false);
|
||||
if (isMobile) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -271,10 +285,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
// 할당된 화면이 없고 URL이 있으면 기존 URL로 이동
|
||||
if (menu.url && menu.url !== "#") {
|
||||
router.push(menu.url);
|
||||
setSidebarOpen(false);
|
||||
if (isMobile) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
} else {
|
||||
// URL도 없고 할당된 화면도 없으면 경고 메시지
|
||||
console.warn("메뉴에 URL이나 할당된 화면이 없습니다:", menu);
|
||||
toast.warning("이 메뉴에는 연결된 페이지나 화면이 없습니다.");
|
||||
}
|
||||
}
|
||||
@@ -295,7 +310,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
await logout();
|
||||
router.push("/login");
|
||||
} catch (error) {
|
||||
console.error("로그아웃 실패:", error);
|
||||
// 로그아웃 실패 시 처리
|
||||
}
|
||||
};
|
||||
|
||||
@@ -306,7 +321,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
return (
|
||||
<div key={menu.id}>
|
||||
<div
|
||||
className={`group flex cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors hover:cursor-pointer ${
|
||||
className={`group flex cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 ease-in-out h-10 ${
|
||||
pathname === menu.url
|
||||
? "bg-gradient-to-br from-slate-100 to-blue-100/40 text-slate-900 border-l-4 border-blue-500"
|
||||
: isExpanded
|
||||
@@ -315,9 +330,9 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
} ${level > 0 ? "ml-6" : ""}`}
|
||||
onClick={() => handleMenuClick(menu)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center min-w-0 flex-1">
|
||||
{menu.icon}
|
||||
<span className="ml-3">{menu.name}</span>
|
||||
<span className="ml-3 truncate" title={menu.name}>{menu.name}</span>
|
||||
</div>
|
||||
{menu.hasChildren && (
|
||||
<div className="ml-auto">
|
||||
@@ -339,8 +354,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
}`}
|
||||
onClick={() => handleMenuClick(child)}
|
||||
>
|
||||
{child.icon}
|
||||
<span className="ml-3">{child.name}</span>
|
||||
<div className="flex items-center min-w-0 flex-1">
|
||||
{child.icon}
|
||||
<span className="ml-3 truncate" title={child.name}>{child.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -369,22 +386,29 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
{/* MainHeader 컴포넌트 사용 */}
|
||||
<MainHeader
|
||||
user={user}
|
||||
onSidebarToggle={() => setSidebarOpen(!sidebarOpen)}
|
||||
onSidebarToggle={() => {
|
||||
// 모바일에서만 토글 동작
|
||||
if (isMobile) {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
}
|
||||
}}
|
||||
onProfileClick={openProfileModal}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
|
||||
<div className="flex flex-1">
|
||||
{/* 모바일 사이드바 오버레이 */}
|
||||
{sidebarOpen && (
|
||||
{sidebarOpen && isMobile && (
|
||||
<div className="fixed inset-0 z-30 bg-black/50 lg:hidden" onClick={() => setSidebarOpen(false)} />
|
||||
)}
|
||||
|
||||
{/* 왼쪽 사이드바 */}
|
||||
<aside
|
||||
className={`${
|
||||
sidebarOpen ? "translate-x-0" : "-translate-x-full"
|
||||
} fixed top-14 left-0 z-40 flex h-full w-64 flex-col border-r border-slate-200 bg-white transition-transform duration-300 lg:relative lg:top-0 lg:z-auto lg:h-full lg:translate-x-0 lg:transform-none`}
|
||||
isMobile
|
||||
? (sidebarOpen ? "translate-x-0" : "-translate-x-full") + " fixed top-14 left-0 z-40"
|
||||
: "translate-x-0 relative top-0 z-auto"
|
||||
} flex h-full w-72 min-w-72 max-w-72 flex-col border-r border-slate-200 bg-white transition-transform duration-300`}
|
||||
>
|
||||
{/* 사이드바 상단 - Admin/User 모드 전환 버튼 (관리자만) */}
|
||||
{(user as ExtendedUserInfo)?.userType === "admin" && (
|
||||
@@ -428,7 +452,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
</aside>
|
||||
|
||||
{/* 가운데 컨텐츠 영역 */}
|
||||
<main className="flex-1 bg-white">{children}</main>
|
||||
<main className="flex-1 min-w-0 bg-white overflow-hidden">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* 프로필 수정 모달 */}
|
||||
|
||||
Reference in New Issue
Block a user