From bd62746d91bae954a73ab1e78258ee4e6f5d7e12 Mon Sep 17 00:00:00 2001 From: wlrnjs Date: Sat, 26 Sep 2026 15:34:53 +0900 Subject: [PATCH] =?UTF-8?q?feat(admin):=20PC=20=ED=99=94=EB=A9=B4=EC=9A=A9?= =?UTF-8?q?=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1280px 이상에서 관리자 화면 왼쪽에 프로필과 메뉴를 담은 사이드바를 고정하고 본문이 나머지 폭을 쓰도록 했습니다. 모바일과 태블릿 스타일은 바꾸지 않았습니다. Co-Authored-By: Claude Opus 5.5 --- .../AdminMenuSection/AdminMenuSection.tsx | 26 ++++++++++++++--- .../_components/AdminProfile/AdminProfile.tsx | 29 +++++++++++++++---- .../_components/AdminSidebar/AdminSidebar.tsx | 23 +++++++++++++++ .../SkeletonAdminProfile.tsx | 2 +- src/app/(admin)/admin/_components/index.ts | 1 + src/app/(admin)/admin/layout.tsx | 8 ++++- .../NoticeEditForm/NoticeEditForm.tsx | 2 +- .../NoticeWriteForm/NoticeWriteForm.tsx | 2 +- src/app/(admin)/admin/page.tsx | 10 +++++-- .../AdminLoginForm/AdminLoginForm.tsx | 2 +- src/app/layout.tsx | 2 +- .../layout/DetailHeader/DetailHeader.tsx | 4 ++- 12 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 src/app/(admin)/admin/_components/AdminSidebar/AdminSidebar.tsx diff --git a/src/app/(admin)/admin/_components/AdminMenuSection/AdminMenuSection.tsx b/src/app/(admin)/admin/_components/AdminMenuSection/AdminMenuSection.tsx index 293e681..bd0225d 100644 --- a/src/app/(admin)/admin/_components/AdminMenuSection/AdminMenuSection.tsx +++ b/src/app/(admin)/admin/_components/AdminMenuSection/AdminMenuSection.tsx @@ -1,5 +1,8 @@ +"use client"; + import { Fragment, ReactNode } from "react"; import Link from "next/link"; +import { usePathname } from "next/navigation"; import { Icon } from "@/components"; import { ADMIN_NAV_SECTIONS } from "../../_constants/ADMIN_NAV_SECTIONS"; import { AdminLogoutButton } from "../_internal"; @@ -36,16 +39,31 @@ interface AdminSectionNavItemProps { } const AdminSectionNavItem = ({ label, items, footer }: AdminSectionNavItemProps) => { + const pathname = usePathname(); + + const isCurrent = (href: string) => + pathname !== null && (pathname === href || pathname.startsWith(`${href}/`)); + return ( -
+

{label}

    {items.map(({ href, title }) => (
  • - - {title} - + + + {title} + +
  • ))} diff --git a/src/app/(admin)/admin/_components/AdminProfile/AdminProfile.tsx b/src/app/(admin)/admin/_components/AdminProfile/AdminProfile.tsx index f6be528..a6fa8fd 100644 --- a/src/app/(admin)/admin/_components/AdminProfile/AdminProfile.tsx +++ b/src/app/(admin)/admin/_components/AdminProfile/AdminProfile.tsx @@ -10,15 +10,31 @@ const AdminProfile = () => { const { email, nickname, profileImg } = data?.result || {}; return ( -
    +
    {isLoading ? ( ) : ( -
    - -
    -

    {nickname}

    - {email} +
    + +
    +

    + {nickname} +

    + + {email} +
    )} @@ -29,6 +45,7 @@ const AdminProfile = () => { aria-label="관리자 프로필 수정" variant="outlined" size="small" + className="pc:w-full" > 프로필 수정 diff --git a/src/app/(admin)/admin/_components/AdminSidebar/AdminSidebar.tsx b/src/app/(admin)/admin/_components/AdminSidebar/AdminSidebar.tsx new file mode 100644 index 0000000..8938c3e --- /dev/null +++ b/src/app/(admin)/admin/_components/AdminSidebar/AdminSidebar.tsx @@ -0,0 +1,23 @@ +import AdminMenuSection from "../AdminMenuSection/AdminMenuSection"; +import AdminProfile from "../AdminProfile/AdminProfile"; + +/** + * PC 화면에서 모든 관리자 화면 왼쪽에 고정되는 사이드바입니다. + * + * @remarks + * - PC 미만에서는 렌더만 하고 숨기며, 그 크기에서는 `/admin` 메인이 같은 프로필과 메뉴를 보여줍니다. + */ + +const AdminSidebar = () => { + return ( + + ); +}; + +export default AdminSidebar; diff --git a/src/app/(admin)/admin/_components/_internal/SkeletonAdminProfile/SkeletonAdminProfile.tsx b/src/app/(admin)/admin/_components/_internal/SkeletonAdminProfile/SkeletonAdminProfile.tsx index 2e71464..112b8fc 100644 --- a/src/app/(admin)/admin/_components/_internal/SkeletonAdminProfile/SkeletonAdminProfile.tsx +++ b/src/app/(admin)/admin/_components/_internal/SkeletonAdminProfile/SkeletonAdminProfile.tsx @@ -1,6 +1,6 @@ const SkeletonAdminProfile = () => { return ( -
    +
    diff --git a/src/app/(admin)/admin/_components/index.ts b/src/app/(admin)/admin/_components/index.ts index 278188b..8c94030 100644 --- a/src/app/(admin)/admin/_components/index.ts +++ b/src/app/(admin)/admin/_components/index.ts @@ -6,3 +6,4 @@ export { default as AdminSearch } from "./AdminSearch/AdminSearch"; export { default as AdminReportsItem } from "./AdminReportsItem/AdminReportsItem"; export { default as AdminDetailSection } from "./AdminDetailSection/AdminDetailSection"; export { default as AdminDropdown } from "./AdminDropdown/AdminDropdown"; +export { default as AdminSidebar } from "./AdminSidebar/AdminSidebar"; diff --git a/src/app/(admin)/admin/layout.tsx b/src/app/(admin)/admin/layout.tsx index 1753f05..f8f7c3f 100644 --- a/src/app/(admin)/admin/layout.tsx +++ b/src/app/(admin)/admin/layout.tsx @@ -1,5 +1,6 @@ import { ReactNode } from "react"; import type { Metadata } from "next"; +import { AdminSidebar } from "./_components"; export const metadata: Metadata = { title: { @@ -34,7 +35,12 @@ export const metadata: Metadata = { }; const layout = ({ children }: { children: ReactNode }) => { - return children; + return ( +
    + +
    {children}
    +
    + ); }; export default layout; diff --git a/src/app/(admin)/admin/notice/write/[id]/_components/NoticeEditForm/NoticeEditForm.tsx b/src/app/(admin)/admin/notice/write/[id]/_components/NoticeEditForm/NoticeEditForm.tsx index 53d7836..c7b134e 100644 --- a/src/app/(admin)/admin/notice/write/[id]/_components/NoticeEditForm/NoticeEditForm.tsx +++ b/src/app/(admin)/admin/notice/write/[id]/_components/NoticeEditForm/NoticeEditForm.tsx @@ -36,7 +36,7 @@ const NoticeEditForm = ({ noticeId }: NoticeEditFormProps) => { -
    +
    diff --git a/src/app/(admin)/admin/notice/write/_components/NoticeWriteForm/NoticeWriteForm.tsx b/src/app/(admin)/admin/notice/write/_components/NoticeWriteForm/NoticeWriteForm.tsx index 1360d3a..678ffe1 100644 --- a/src/app/(admin)/admin/notice/write/_components/NoticeWriteForm/NoticeWriteForm.tsx +++ b/src/app/(admin)/admin/notice/write/_components/NoticeWriteForm/NoticeWriteForm.tsx @@ -24,7 +24,7 @@ const NoticeWriteForm = ({ methods }: { methods: UseFormReturn -
    +
    diff --git a/src/app/(admin)/admin/page.tsx b/src/app/(admin)/admin/page.tsx index 426b7dc..0068069 100644 --- a/src/app/(admin)/admin/page.tsx +++ b/src/app/(admin)/admin/page.tsx @@ -10,8 +10,14 @@ const page = async () => {

    관리자 페이지

    - - +
    + + +
    + +

    + 왼쪽 메뉴에서 관리할 항목을 선택해 주세요. +

    ); }; diff --git a/src/app/(auth)/login/_components/AdminLoginForm/AdminLoginForm.tsx b/src/app/(auth)/login/_components/AdminLoginForm/AdminLoginForm.tsx index 0302079..ed83192 100644 --- a/src/app/(auth)/login/_components/AdminLoginForm/AdminLoginForm.tsx +++ b/src/app/(auth)/login/_components/AdminLoginForm/AdminLoginForm.tsx @@ -39,7 +39,7 @@ const AdminLoginForm = () => { return (
    -
    +
    diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9e6c235..e249020 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -68,7 +68,7 @@ export default function RootLayout({ return ( - +
    {children}
    diff --git a/src/components/layout/DetailHeader/DetailHeader.tsx b/src/components/layout/DetailHeader/DetailHeader.tsx index be1e461..8f507ba 100644 --- a/src/components/layout/DetailHeader/DetailHeader.tsx +++ b/src/components/layout/DetailHeader/DetailHeader.tsx @@ -12,6 +12,7 @@ import { cn } from "@/utils"; * - `"use client"`이며 `next/navigation`의 `useRouter`로 뒤로가기 동작을 처리합니다. * - `onBack`이 있으면 뒤로 버튼은 그만 호출하고, 없으면 `sessionStorage`의 `__fmi_history_count`를 보고 `router.back()` 또는 `/`로 이동합니다. * - `fixed` 헤더 아래 콘텐츠가 가리지 않도록 동일 높이의 자리 표시 `div`를 둡니다. + * - PC에서는 사이드바 옆 본문 폭을 따라가도록 `sticky`로 바뀌고 자리 표시 `div`는 숨깁니다. * - 오른쪽 액션은 `DetailHeaderParts`(저장, 검색, 즐겨찾기, 메뉴 등)를 `children`으로 넣는 패턴을 씁니다. * * @author hyungjun @@ -81,6 +82,7 @@ const DetailHeader = ({ title = "", children, onBack, onBeforeBack }: DetailHead
    @@ -101,7 +103,7 @@ const DetailHeader = ({ title = "", children, onBack, onBeforeBack }: DetailHead
    )}
    -
    +
    ); };