공용 — PageHeader 신설 (메뉴명 자동 매칭) + M-BOM 메뉴명 복원
CompactFilterBar 마이그레이션 과정에서 M-BOM 페이지 상단 메뉴명/설명이 사라진
회귀를 해결. customer-cs/cs 의 페이지 헤더 패턴을 공용 컴포넌트로 추출.
신설:
- components/common/PageHeader.tsx
· usePathname() + useMenu() 자동 매칭 → menu_info.menu_name_kor + menu_desc
· 명시 props (title/description/actions) 지원
· 동적 라우트 prefix fallback (/foo/123 → /foo 매칭)
적용:
- production/mbom/page.tsx 상단에 <PageHeader /> 1줄 추가
DB:
- menu_info.menu_desc 보강 (objid 100016/100032)
"생산용 BOM 트리 + read-only 조회 (운영판 mBomMgmtList 1:1)"
메모리: feedback_compact_search_pattern.md 갱신
- PageHeader 도 의무 사용 컴포넌트 목록에 추가
- 페이지 구조 표준 코드 예시 명시
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- M-BOM 관리 메뉴 menu_desc 보강 (PageHeader 자동 매칭용)
|
||||||
|
-- 2026-05-13
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
UPDATE menu_info
|
||||||
|
SET menu_desc = '생산용 BOM 트리 + read-only 조회 (운영판 mBomMgmtList 1:1)'
|
||||||
|
WHERE objid IN (100016, 100032);
|
||||||
@@ -15,6 +15,7 @@ import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
|
|||||||
import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect";
|
import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect";
|
||||||
import { CustomerSelect } from "@/components/common/CustomerSelect";
|
import { CustomerSelect } from "@/components/common/CustomerSelect";
|
||||||
import { CompactFilterBar, CompactFilterField, CompactDateRange } from "@/components/common/CompactFilterBar";
|
import { CompactFilterBar, CompactFilterField, CompactDateRange } from "@/components/common/CompactFilterBar";
|
||||||
|
import { PageHeader } from "@/components/common/PageHeader";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
import { mbomApi, MbomListFilter, MbomRow } from "@/lib/api/mbom";
|
import { mbomApi, MbomListFilter, MbomRow } from "@/lib/api/mbom";
|
||||||
import { MbomDetailDialog } from "@/components/production/MbomDetailDialog";
|
import { MbomDetailDialog } from "@/components/production/MbomDetailDialog";
|
||||||
@@ -140,6 +141,7 @@ export default function MbomMgmtPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col gap-2 p-2">
|
<div className="flex h-full flex-col gap-2 p-2">
|
||||||
|
<PageHeader />
|
||||||
<CompactFilterBar
|
<CompactFilterBar
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PageHeader — 페이지 상단 메뉴명 + 설명 + 액션 슬롯.
|
||||||
|
*
|
||||||
|
* customer-cs/cs 페이지 패턴 1:1 추출. 모든 RPS 메뉴 페이지의 상단에 의무 배치.
|
||||||
|
*
|
||||||
|
* 자동 매칭:
|
||||||
|
* - usePathname() 으로 현재 경로를 잡고 MenuContext 의 userMenus/adminMenus 에서
|
||||||
|
* menu_url 이 일치하는 항목을 찾아 menu_name_kor / menu_desc 를 표시.
|
||||||
|
*
|
||||||
|
* 명시 지정:
|
||||||
|
* <PageHeader title="M-BOM 관리" description="생산용 BOM 트리 + 본 편집" />
|
||||||
|
*
|
||||||
|
* 액션 슬롯:
|
||||||
|
* <PageHeader actions={<><Button>새로고침</Button><Button>등록</Button></>} />
|
||||||
|
*
|
||||||
|
* 원칙:
|
||||||
|
* - 모든 page.tsx 의 최상위 자식으로 <PageHeader /> 를 배치한다.
|
||||||
|
* - 메뉴명이 menu_info 에 있다면 props 없이도 자동 매칭되므로 그냥 <PageHeader /> 면 충분.
|
||||||
|
* - 액션 버튼이 있으면 actions 슬롯에만 넣는다 (검색 영역에 두지 말 것).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { useMenu } from "@/contexts/MenuContext";
|
||||||
|
import type { MenuItem } from "@/lib/api/menu";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface PageHeaderProps {
|
||||||
|
/** 명시 메뉴명. 없으면 usePathname() + MenuContext 자동 매칭. */
|
||||||
|
title?: string;
|
||||||
|
/** 명시 설명. 없으면 자동 매칭 결과의 menu_desc. */
|
||||||
|
description?: string;
|
||||||
|
/** 우측 액션 슬롯 (새로고침/등록/엑셀 다운로드 등) */
|
||||||
|
actions?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findByUrl(menus: MenuItem[], pathname: string): MenuItem | null {
|
||||||
|
// MenuContext 는 flat 리스트. 정확 매칭 우선, 없으면 prefix(파라미터 라우트 대응).
|
||||||
|
for (const m of menus) {
|
||||||
|
if (m.menu_url && m.menu_url === pathname) return m;
|
||||||
|
}
|
||||||
|
// 동적 라우트 fallback: /COMPANY_16/foo/123 → /COMPANY_16/foo 매칭
|
||||||
|
let best: MenuItem | null = null;
|
||||||
|
let bestLen = 0;
|
||||||
|
for (const m of menus) {
|
||||||
|
if (m.menu_url && pathname.startsWith(m.menu_url) && m.menu_url.length > bestLen) {
|
||||||
|
best = m;
|
||||||
|
bestLen = m.menu_url.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageHeader({ title, description, actions, className }: PageHeaderProps) {
|
||||||
|
const pathname = usePathname() ?? "";
|
||||||
|
// useMenu() 가 Provider 밖에서 호출되면 throw — 안전하게 try
|
||||||
|
let menu: MenuItem | null = null;
|
||||||
|
try {
|
||||||
|
const { userMenus, adminMenus } = useMenu();
|
||||||
|
menu = findByUrl(userMenus, pathname) ?? findByUrl(adminMenus, pathname);
|
||||||
|
} catch {
|
||||||
|
/* MenuProvider 밖 (스토리북/테스트 등) — 자동 매칭 생략 */
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedTitle = title ?? menu?.menu_name_kor ?? "";
|
||||||
|
const resolvedDesc = description ?? menu?.menu_desc ?? "";
|
||||||
|
|
||||||
|
if (!resolvedTitle && !resolvedDesc && !actions) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("flex flex-shrink-0 items-end justify-between gap-3 border-b pb-3", className)}>
|
||||||
|
<div>
|
||||||
|
{resolvedTitle && (
|
||||||
|
<h1 className="text-xl font-bold tracking-tight">{resolvedTitle}</h1>
|
||||||
|
)}
|
||||||
|
{resolvedDesc && (
|
||||||
|
<p className="text-xs text-muted-foreground">{resolvedDesc}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{actions && <div className="flex items-center gap-1.5">{actions}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user