161 lines
5.2 KiB
TypeScript
161 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
import { useMenu } from "@/hooks/useMenu";
|
|
import { useProfile } from "@/hooks/useProfile";
|
|
import { MainHeader } from "@/components/layout/MainHeader";
|
|
import { MainSidebar } from "@/components/layout/MainSidebar";
|
|
import { ProfileModal } from "@/components/layout/ProfileModal";
|
|
import { LAYOUT_CONFIG } from "@/constants/layout";
|
|
|
|
/**
|
|
* 메인 레이아웃 컴포넌트
|
|
* 비즈니스 로직은 커스텀 훅들에서 처리하고, UI 컴포넌트들을 조합하여 구성
|
|
*/
|
|
export default function MainLayout({ children }: { children: React.ReactNode }) {
|
|
const { user, logout, refreshUserData, loading: authLoading } = useAuth();
|
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false); // 사이드바 상태
|
|
const [isMobile, setIsMobile] = useState(false); // 모바일 여부
|
|
|
|
// 화면 크기 감지
|
|
useEffect(() => {
|
|
const checkMobile = () => {
|
|
const mobile = window.innerWidth < 768; // md breakpoint
|
|
setIsMobile(mobile);
|
|
|
|
// 모바일에서는 사이드바를 기본적으로 닫아둠
|
|
if (mobile) {
|
|
setIsSidebarOpen(false);
|
|
}
|
|
// PC에서는 사이드바 상태를 건드리지 않음 (항상 표시)
|
|
};
|
|
|
|
checkMobile();
|
|
window.addEventListener("resize", checkMobile);
|
|
return () => window.removeEventListener("resize", checkMobile);
|
|
}, []);
|
|
|
|
// 메뉴 관련 로직
|
|
const { menuList, expandedMenus, handleMenuClick } = useMenu(user, authLoading);
|
|
|
|
// 프로필 관련 로직
|
|
const {
|
|
isModalOpen,
|
|
formData,
|
|
selectedImage,
|
|
isSaving,
|
|
alertModal,
|
|
closeAlert,
|
|
openProfileModal,
|
|
closeProfileModal,
|
|
updateFormData,
|
|
selectImage,
|
|
removeImage,
|
|
saveProfile,
|
|
} = useProfile(user, refreshUserData);
|
|
|
|
/**
|
|
* 사이드바 토글 처리 (모바일에서만 동작)
|
|
*/
|
|
const handleSidebarToggle = () => {
|
|
if (isMobile) {
|
|
// 모바일에서만 토글 동작
|
|
setIsSidebarOpen(!isSidebarOpen);
|
|
}
|
|
// PC에서는 아무것도 하지 않음 (항상 표시)
|
|
};
|
|
|
|
/**
|
|
* 로그아웃 처리
|
|
*/
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
};
|
|
|
|
return (
|
|
<div className="bg-background flex h-screen flex-col">
|
|
<MainHeader
|
|
user={user}
|
|
onSidebarToggle={handleSidebarToggle}
|
|
onProfileClick={openProfileModal}
|
|
onLogout={handleLogout}
|
|
/>
|
|
|
|
<div className="relative flex-1">
|
|
{/* 데스크톱 고정 사이드바 - md 이상에서 항상 표시 */}
|
|
<aside
|
|
className={`fixed top-14 left-0 z-40 hidden md:block ${LAYOUT_CONFIG.SIDEBAR.WIDTH} bg-background h-[calc(100vh-3.5rem)] border-r`}
|
|
>
|
|
<MainSidebar
|
|
menuList={menuList}
|
|
expandedMenus={expandedMenus}
|
|
onMenuClick={handleMenuClick}
|
|
className="h-full p-4"
|
|
/>
|
|
</aside>
|
|
|
|
{/* 모바일 오버레이 사이드바 - md 미만에서만 표시 */}
|
|
{isMobile && (
|
|
<div
|
|
className={`fixed top-14 right-0 bottom-0 left-0 z-50 flex transition-all duration-300 ease-in-out md:hidden ${
|
|
isSidebarOpen ? "opacity-100" : "pointer-events-none opacity-0"
|
|
}`}
|
|
>
|
|
{/* 배경 오버레이 */}
|
|
<div
|
|
className={`fixed top-14 right-0 bottom-0 left-0 bg-black/50 transition-opacity duration-300 ${
|
|
isSidebarOpen ? "opacity-100" : "opacity-0"
|
|
}`}
|
|
onClick={() => setIsSidebarOpen(false)}
|
|
/>
|
|
{/* 사이드바 */}
|
|
<aside
|
|
className={`relative ${LAYOUT_CONFIG.SIDEBAR.WIDTH} bg-background flex h-full transform flex-col shadow-lg transition-transform duration-300 ease-in-out ${
|
|
isSidebarOpen ? "translate-x-0" : "-translate-x-full"
|
|
}`}
|
|
>
|
|
{/* 메뉴 리스트 */}
|
|
<div className="flex-1 overflow-y-auto">
|
|
<MainSidebar
|
|
menuList={menuList}
|
|
expandedMenus={expandedMenus}
|
|
onMenuClick={(menu) => {
|
|
handleMenuClick(menu);
|
|
// 모바일에서 메뉴 클릭 시 사이드바 닫기
|
|
if (isMobile && (!menu.children || menu.children.length === 0)) {
|
|
setIsSidebarOpen(false);
|
|
}
|
|
}}
|
|
className="p-4"
|
|
/>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
)}
|
|
|
|
{/* Main Content - 반응형 여백 적용 */}
|
|
<main className="ml-0 w-full transition-all duration-300 md:ml-64 md:w-auto">
|
|
<div className="h-full p-6">{children}</div>
|
|
</main>
|
|
</div>
|
|
|
|
{/* 프로필 수정 모달 */}
|
|
<ProfileModal
|
|
isOpen={isModalOpen}
|
|
user={user}
|
|
formData={formData}
|
|
selectedImage={selectedImage}
|
|
isSaving={isSaving}
|
|
alertModal={alertModal}
|
|
onClose={closeProfileModal}
|
|
onFormChange={updateFormData}
|
|
onImageSelect={selectImage}
|
|
onImageRemove={removeImage}
|
|
onSave={saveProfile}
|
|
onAlertClose={closeAlert}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|