|
|
|
|
@@ -407,12 +407,12 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
return (
|
|
|
|
|
<div key={menu.id}>
|
|
|
|
|
<div
|
|
|
|
|
className={`group flex h-10 cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 ease-in-out ${
|
|
|
|
|
className={`group flex h-10 cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-150 ease-in-out ${
|
|
|
|
|
pathname === menu.url
|
|
|
|
|
? "border-primary border-l-4 bg-gradient-to-br from-slate-100 to-blue-100/40 text-slate-900"
|
|
|
|
|
? "bg-primary/10 text-primary font-semibold"
|
|
|
|
|
: isExpanded
|
|
|
|
|
? "bg-slate-100 text-slate-900"
|
|
|
|
|
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900"
|
|
|
|
|
? "bg-accent text-foreground"
|
|
|
|
|
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
|
|
|
} ${level > 0 ? "ml-6" : ""}`}
|
|
|
|
|
onClick={() => handleMenuClick(menu)}
|
|
|
|
|
>
|
|
|
|
|
@@ -431,14 +431,14 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
|
|
|
|
|
{/* 서브메뉴 */}
|
|
|
|
|
{menu.hasChildren && isExpanded && (
|
|
|
|
|
<div className="mt-1 space-y-1 pl-6">
|
|
|
|
|
<div className="mt-0.5 space-y-0.5 pl-9">
|
|
|
|
|
{menu.children?.map((child: any) => (
|
|
|
|
|
<div
|
|
|
|
|
key={child.id}
|
|
|
|
|
className={`flex cursor-pointer items-center rounded-lg px-3 py-2 text-sm transition-colors hover:cursor-pointer ${
|
|
|
|
|
className={`flex cursor-pointer items-center rounded-lg px-3 py-2 text-sm transition-colors duration-150 hover:cursor-pointer ${
|
|
|
|
|
pathname === child.url
|
|
|
|
|
? "border-primary border-l-4 bg-gradient-to-br from-slate-100 to-blue-100/40 text-slate-900"
|
|
|
|
|
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900"
|
|
|
|
|
? "bg-primary/10 text-primary font-semibold"
|
|
|
|
|
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
onClick={() => handleMenuClick(child)}
|
|
|
|
|
>
|
|
|
|
|
@@ -459,7 +459,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
// 프리뷰 모드: 사이드바/헤더 없이 화면만 표시 (인증 대기 없이 즉시 렌더링)
|
|
|
|
|
if (isPreviewMode) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="h-screen w-full overflow-auto bg-white p-4">
|
|
|
|
|
<div className="h-screen w-full overflow-auto bg-background p-4">
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
@@ -481,10 +481,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
const uiMenus = convertMenuToUI(currentMenus, user as ExtendedUserInfo);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-screen flex-col bg-white">
|
|
|
|
|
<div className="flex h-screen flex-col bg-background">
|
|
|
|
|
{/* 모바일 헤더 - 모바일에서만 표시 */}
|
|
|
|
|
{isMobile && (
|
|
|
|
|
<header className="fixed top-0 left-0 right-0 z-50 flex h-14 items-center justify-between border-b border-slate-200 bg-white px-4">
|
|
|
|
|
<header className="fixed top-0 left-0 right-0 z-50 flex h-14 items-center justify-between border-b border-border bg-background/95 backdrop-blur-sm px-4">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
{/* 햄버거 메뉴 버튼 */}
|
|
|
|
|
<SideMenu onSidebarToggle={() => setSidebarOpen(!sidebarOpen)} />
|
|
|
|
|
@@ -493,7 +493,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
{/* 사용자 드롭다운 */}
|
|
|
|
|
<DropdownMenu modal={false}>
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
<button className="flex items-center gap-2 rounded-lg px-2 py-1 transition-colors hover:bg-slate-100">
|
|
|
|
|
<button className="flex items-center gap-2 rounded-lg px-2 py-1 transition-colors hover:bg-accent">
|
|
|
|
|
<div className="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-full">
|
|
|
|
|
{user.photo && user.photo.trim() !== "" && user.photo !== "null" ? (
|
|
|
|
|
<img
|
|
|
|
|
@@ -502,7 +502,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
className="aspect-square h-full w-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center rounded-full bg-slate-200 text-sm font-semibold text-slate-700">
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-semibold text-foreground">
|
|
|
|
|
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
@@ -543,7 +543,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
<div className={`flex flex-1 ${isMobile ? "pt-14" : ""}`}>
|
|
|
|
|
{/* 모바일 사이드바 오버레이 */}
|
|
|
|
|
{sidebarOpen && isMobile && (
|
|
|
|
|
<div className="fixed inset-0 z-30 bg-black/50 lg:hidden" onClick={() => setSidebarOpen(false)} />
|
|
|
|
|
<div className="fixed inset-0 z-30 bg-black/40 backdrop-blur-sm lg:hidden" onClick={() => setSidebarOpen(false)} />
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 왼쪽 사이드바 */}
|
|
|
|
|
@@ -552,11 +552,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
isMobile
|
|
|
|
|
? (sidebarOpen ? "translate-x-0" : "-translate-x-full") + " fixed top-14 left-0 z-40 h-[calc(100vh-56px)]"
|
|
|
|
|
: "relative z-auto h-screen translate-x-0"
|
|
|
|
|
} flex w-[200px] flex-col border-r border-slate-200 bg-white transition-transform duration-300`}
|
|
|
|
|
} flex w-[220px] lg:w-[240px] flex-col border-r border-border bg-background transition-transform duration-300`}
|
|
|
|
|
>
|
|
|
|
|
{/* 사이드바 최상단 - 로고 (데스크톱에서만 표시) */}
|
|
|
|
|
{!isMobile && (
|
|
|
|
|
<div className="flex h-14 items-center justify-between border-b border-slate-200 px-4">
|
|
|
|
|
<div className="flex h-14 items-center justify-between border-b border-border px-4">
|
|
|
|
|
<Logo />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
@@ -580,14 +580,14 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
{((user as ExtendedUserInfo)?.userType === "SUPER_ADMIN" ||
|
|
|
|
|
(user as ExtendedUserInfo)?.userType === "COMPANY_ADMIN" ||
|
|
|
|
|
(user as ExtendedUserInfo)?.userType === "admin") && (
|
|
|
|
|
<div className="space-y-2 border-b border-slate-200 p-3">
|
|
|
|
|
<div className="space-y-2 border-b border-border p-3">
|
|
|
|
|
{/* 관리자/사용자 메뉴 전환 */}
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleModeSwitch}
|
|
|
|
|
className={`flex w-full items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 hover:cursor-pointer ${
|
|
|
|
|
className={`flex w-full items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-150 hover:cursor-pointer ${
|
|
|
|
|
isAdminMode
|
|
|
|
|
? "border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100"
|
|
|
|
|
: "border-primary/20 bg-accent hover:bg-primary/20 border text-blue-700"
|
|
|
|
|
? "border border-warning/20 bg-warning/10 text-warning hover:bg-warning/20"
|
|
|
|
|
: "border border-primary/20 bg-primary/10 text-primary hover:bg-primary/20"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{isAdminMode ? (
|
|
|
|
|
@@ -607,7 +607,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
{(user as ExtendedUserInfo)?.userType === "SUPER_ADMIN" && (
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() => { console.log("🔴 회사 선택 버튼 클릭!"); setShowCompanySwitcher(true); }}
|
|
|
|
|
className="flex w-full items-center justify-center gap-2 rounded-lg border border-purple-200 bg-purple-50 px-3 py-2 text-sm font-medium text-purple-700 transition-colors duration-200 hover:cursor-pointer hover:bg-purple-100"
|
|
|
|
|
className="flex w-full items-center justify-center gap-2 rounded-lg border border-primary/20 bg-primary/10 px-3 py-2 text-sm font-medium text-primary transition-colors duration-150 hover:cursor-pointer hover:bg-primary/20"
|
|
|
|
|
>
|
|
|
|
|
<Building2 className="h-4 w-4" />
|
|
|
|
|
회사 선택
|
|
|
|
|
@@ -618,11 +618,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
|
|
|
|
|
{/* 메뉴 영역 */}
|
|
|
|
|
<div className="flex-1 overflow-y-auto py-4">
|
|
|
|
|
<nav className="space-y-1 px-3">
|
|
|
|
|
<nav className="space-y-0.5 px-3">
|
|
|
|
|
{loading ? (
|
|
|
|
|
<div className="animate-pulse space-y-2">
|
|
|
|
|
{[...Array(5)].map((_, i) => (
|
|
|
|
|
<div key={i} className="h-8 rounded bg-slate-200"></div>
|
|
|
|
|
<div key={i} className="h-8 rounded bg-muted"></div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
@@ -632,10 +632,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 사이드바 하단 - 사용자 프로필 */}
|
|
|
|
|
<div className="border-t border-slate-200 p-3">
|
|
|
|
|
<div className="border-t border-border bg-muted/30 p-3">
|
|
|
|
|
<DropdownMenu modal={false}>
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
<button className="flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-colors hover:bg-slate-100">
|
|
|
|
|
<button className="flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-colors hover:bg-accent">
|
|
|
|
|
{/* 프로필 아바타 */}
|
|
|
|
|
<div className="relative flex h-9 w-9 shrink-0 overflow-hidden rounded-full">
|
|
|
|
|
{user.photo && user.photo.trim() !== "" && user.photo !== "null" ? (
|
|
|
|
|
@@ -645,17 +645,17 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
className="aspect-square h-full w-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center rounded-full bg-slate-200 text-sm font-semibold text-slate-700">
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-semibold text-foreground">
|
|
|
|
|
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{/* 사용자 정보 */}
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
<p className="truncate text-sm font-medium text-slate-900">
|
|
|
|
|
<p className="truncate text-sm font-medium text-foreground">
|
|
|
|
|
{user.userName || "사용자"}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="truncate text-xs text-slate-500">
|
|
|
|
|
<p className="truncate text-xs text-muted-foreground">
|
|
|
|
|
{user.deptName || user.email || user.userId}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -673,7 +673,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
className="aspect-square h-full w-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center rounded-full bg-slate-200 text-base font-semibold text-slate-700">
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center rounded-full bg-muted text-base font-semibold text-foreground">
|
|
|
|
|
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
@@ -713,7 +713,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
{/* 가운데 컨텐츠 영역 - 스크롤 가능 */}
|
|
|
|
|
<main className={`min-w-0 flex-1 overflow-auto bg-white ${isMobile ? "h-[calc(100vh-56px)]" : "h-screen"}`}>
|
|
|
|
|
<main className={`min-w-0 flex-1 overflow-auto bg-background ${isMobile ? "h-[calc(100vh-56px)]" : "h-screen"}`}>
|
|
|
|
|
{children}
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
|