fix(layout): 모모 페이지를 (main)/m/* 로 이동 — 기존 레이아웃 그대로 사용
Deploy momo-erp / deploy (push) Successful in 46s
Deploy momo-erp / deploy (push) Successful in 46s
문제: /m/* 별도 레이아웃 때문에 메뉴 클릭 시 사이드바·헤더·디자인이 통째로
바뀜. 사용자가 원한 건 기존 (main) 레이아웃 안에서 같은 디자인으로 동작.
조치:
- src/app/m/* 전체를 src/app/(main)/m/* 로 이동
- src/app/m/layout.tsx + src/components/momo/{sidebar,header} 삭제
- (main)/layout.tsx 가 자동으로 모든 모모 페이지를 감싸 → Sidebar + Header
+ Content 일관 표시
- URL은 그대로 (/m/dashboard 등) — menu_info DB 변경 불필요
→ plm_admin 로그인 후 [사용자] 그룹의 모모 메뉴 클릭 시 동일한 (main)
사이드바/헤더 유지하면서 콘텐츠만 교체. 디자인 일관 + 작동 정상.
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getSession } from "@/lib/session";
|
||||
import { MomoSidebar } from "@/components/momo/sidebar";
|
||||
import { MomoHeader } from "@/components/momo/header";
|
||||
|
||||
export default async function MomoLayout({ children }: { children: React.ReactNode }) {
|
||||
const user = await getSession();
|
||||
if (!user) redirect("/login");
|
||||
|
||||
// 역할 결정:
|
||||
// - MOMO 가입 사용자 → user.role 그대로
|
||||
// - FITO 슈퍼관리자(plm_admin 등) → ADMIN 으로 취급
|
||||
// - 그 외 FITO 일반 사용자 → ADMIN 으로 취급 (메뉴 관리 화면에서 모모 진입)
|
||||
const role: "USER" | "ADMIN" = user.role ?? (user.isAdmin ? "ADMIN" : "ADMIN");
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex bg-slate-50">
|
||||
<MomoSidebar role={role} />
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
<MomoHeader companyName={user.companyName || user.userName} role={role} email={user.email} />
|
||||
<main className="flex-1 p-6 overflow-x-auto">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { LogOut, ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export function MomoHeader({ companyName, role, email }: { companyName?: string; role: "USER" | "ADMIN"; email: string }) {
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const logout = async () => {
|
||||
await fetch("/api/auth/logout", { method: "POST" });
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="h-16 bg-white border-b border-slate-200 flex items-center justify-end px-6 gap-3">
|
||||
<span className={`px-2.5 py-1 rounded-full text-[11px] font-bold ${role === "ADMIN" ? "bg-emerald-100 text-emerald-800" : "bg-slate-100 text-slate-700"}`}>
|
||||
{role === "ADMIN" ? "관리자" : "거래처"}
|
||||
</span>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="flex items-center gap-2 text-sm text-slate-700 hover:bg-slate-50 px-3 h-9 rounded-lg"
|
||||
>
|
||||
<div className="w-7 h-7 rounded-full bg-emerald-700 text-white text-xs font-bold flex items-center justify-center">
|
||||
{(companyName || "M")[0]}
|
||||
</div>
|
||||
<span className="font-semibold">{companyName || email}</span>
|
||||
<ChevronDown size={14} className="text-slate-400" />
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute right-0 mt-2 w-56 bg-white border border-slate-200 rounded-xl shadow-lg overflow-hidden z-50">
|
||||
<div className="px-4 py-3 border-b border-slate-100">
|
||||
<div className="font-semibold text-sm">{companyName}</div>
|
||||
<div className="text-xs text-slate-500">{email}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="w-full px-4 py-2.5 text-left text-sm hover:bg-slate-50 flex items-center gap-2 text-slate-700"
|
||||
>
|
||||
<LogOut size={14} /> 로그아웃
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
LayoutDashboard, Package, ShoppingCart, Warehouse, TrendingUp,
|
||||
Building2, ClipboardList, Truck, Receipt, PackagePlus, Wallet,
|
||||
} from "lucide-react";
|
||||
|
||||
interface MenuLink {
|
||||
href: string;
|
||||
label: string;
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
roles: ("USER" | "ADMIN")[];
|
||||
group?: string;
|
||||
}
|
||||
|
||||
const MENU: MenuLink[] = [
|
||||
{ href: "/m/dashboard", label: "대시보드", icon: LayoutDashboard, roles: ["USER", "ADMIN"] },
|
||||
|
||||
{ href: "/m/items", label: "품목 검색", icon: Package, roles: ["USER"], group: "주문" },
|
||||
{ href: "/m/orders/new", label: "출고 요청", icon: ShoppingCart, roles: ["USER"], group: "주문" },
|
||||
{ href: "/m/orders", label: "내 출고 이력", icon: ClipboardList, roles: ["USER"], group: "주문" },
|
||||
|
||||
{ href: "/m/admin/items", label: "품목 관리", icon: Package, roles: ["ADMIN"], group: "마스터" },
|
||||
{ href: "/m/admin/vendors", label: "매입처 관리", icon: Building2, roles: ["ADMIN"], group: "마스터" },
|
||||
{ href: "/m/admin/warehouses", label: "창고 관리", icon: Warehouse, roles: ["ADMIN"], group: "마스터" },
|
||||
{ href: "/m/admin/procurements", label: "매입 발주", icon: Truck, roles: ["ADMIN"], group: "매입" },
|
||||
{ href: "/m/admin/inbounds", label: "입고 처리", icon: PackagePlus, roles: ["ADMIN"], group: "매입" },
|
||||
{ href: "/m/admin/inventory", label: "재고 관리", icon: Warehouse, roles: ["ADMIN"], group: "매입" },
|
||||
|
||||
{ href: "/m/admin/orders", label: "출고 관리", icon: ClipboardList, roles: ["ADMIN"], group: "출고/정산" },
|
||||
{ href: "/m/admin/payments", label: "입금 관리", icon: Wallet, roles: ["ADMIN"], group: "출고/정산" },
|
||||
{ href: "/m/admin/invoices", label: "계산서 발행", icon: Receipt, roles: ["ADMIN"], group: "출고/정산" },
|
||||
|
||||
{ href: "/m/admin/statistics", label: "월간 매출", icon: TrendingUp, roles: ["ADMIN"], group: "통계" },
|
||||
{ href: "/m/admin/statistics/daily", label: "일자별", icon: TrendingUp, roles: ["ADMIN"], group: "통계" },
|
||||
{ href: "/m/admin/statistics/margin", label: "원가/마진", icon: TrendingUp, roles: ["ADMIN"], group: "통계" },
|
||||
];
|
||||
|
||||
export function MomoSidebar({ role }: { role: "USER" | "ADMIN" }) {
|
||||
const pathname = usePathname();
|
||||
const items = MENU.filter((m) => m.roles.includes(role));
|
||||
|
||||
const grouped: Record<string, MenuLink[]> = {};
|
||||
for (const m of items) {
|
||||
const g = m.group || "_top";
|
||||
(grouped[g] ||= []).push(m);
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="w-60 shrink-0 bg-gradient-to-b from-[#0d3b24] to-[#1b5e3a] text-white flex flex-col">
|
||||
<Link href="/m/dashboard" className="px-6 h-16 flex items-center gap-2.5 border-b border-white/10 hover:bg-white/5 transition">
|
||||
<img src="/momo-icon.svg" alt="" className="w-8 h-8" />
|
||||
<div>
|
||||
<div className="text-[11px] tracking-widest text-emerald-200/80 leading-none">MOMO</div>
|
||||
<div className="text-sm font-bold leading-tight">유통관리</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<nav className="flex-1 py-4 px-2 space-y-2 overflow-y-auto">
|
||||
{Object.entries(grouped).map(([group, list]) => (
|
||||
<div key={group}>
|
||||
{group !== "_top" && (
|
||||
<div className="px-3 py-1.5 text-[10px] font-bold tracking-widest text-emerald-300/60 uppercase">
|
||||
{group}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-0.5">
|
||||
{list.map((it) => {
|
||||
const Icon = it.icon;
|
||||
const active = pathname === it.href || pathname.startsWith(it.href + "/");
|
||||
return (
|
||||
<Link
|
||||
key={it.href}
|
||||
href={it.href}
|
||||
className={
|
||||
"flex items-center gap-3 px-4 h-9 rounded-lg text-sm transition " +
|
||||
(active
|
||||
? "bg-white/15 text-white font-semibold"
|
||||
: "text-emerald-100/80 hover:bg-white/10 hover:text-white")
|
||||
}
|
||||
>
|
||||
<Icon size={15} />
|
||||
{it.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="p-4 border-t border-white/10 text-[11px] text-emerald-200/60">© 2026 MOMO Distribution</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user