feat(WEB): 리스트 페이지 UI 레이아웃 표준화

- 공통 레이아웃 패턴 적용: [달력] → [프리셋] → [검색창] → [버튼들]
- beforeTableContent → headerActions + createButton 마이그레이션
- DateRangeSelector extraActions prop 활용하여 검색창 통합
- PricingListClient 테이블 행 클릭 → 상세 이동 기능 추가
- 회계 관련 페이지 (입금/출금/매입/매출/어음/카드/예상지출 등) 정리
- 건설 관련 페이지 검색 영역 정리
- 부모 메뉴 리다이렉트 컴포넌트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-26 22:04:36 +09:00
parent ff93ab7fa2
commit 1f6b592b9f
65 changed files with 1974 additions and 503 deletions

View File

@@ -0,0 +1,80 @@
'use client';
import { useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';
interface ParentMenuRedirectProps {
/** 현재 부모 메뉴 경로 (예: '/accounting') */
parentPath: string;
/** 메뉴 데이터를 찾지 못했을 때 사용할 기본 첫 번째 자식 경로 */
fallbackPath: string;
}
/**
* 부모 메뉴 URL 접근 시 첫 번째 자식 메뉴로 동적 리다이렉트
*
* localStorage에 저장된 메뉴 구조를 읽어서 해당 부모의 첫 번째 자식으로 이동합니다.
* 메뉴 구조가 변경되어도 자동으로 대응됩니다.
*/
export function ParentMenuRedirect({ parentPath, fallbackPath }: ParentMenuRedirectProps) {
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
try {
// localStorage에서 user 데이터 읽기
const userData = localStorage.getItem('user');
if (!userData) {
router.replace(fallbackPath);
return;
}
const parsed = JSON.parse(userData);
const menuItems = parsed.menu;
if (!menuItems || !Array.isArray(menuItems)) {
router.replace(fallbackPath);
return;
}
// 현재 부모 메뉴 찾기 (재귀적으로 검색)
const findParentMenu = (items: any[], targetPath: string): any | null => {
for (const item of items) {
// 경로가 일치하는지 확인 (locale prefix 제거 후 비교)
const itemPath = item.path?.replace(/^\/[a-z]{2}\//, '/') || '';
if (itemPath === targetPath || item.path === targetPath) {
return item;
}
// 자식 메뉴에서 검색
if (item.children && item.children.length > 0) {
const found = findParentMenu(item.children, targetPath);
if (found) return found;
}
}
return null;
};
const parentMenu = findParentMenu(menuItems, parentPath);
if (parentMenu && parentMenu.children && parentMenu.children.length > 0) {
// 첫 번째 자식 메뉴의 경로로 리다이렉트
const firstChild = parentMenu.children[0];
const firstChildPath = firstChild.path?.replace(/^\/[a-z]{2}\//, '/') || fallbackPath;
router.replace(firstChildPath);
} else {
// 자식이 없으면 fallback으로 이동
router.replace(fallbackPath);
}
} catch (error) {
console.error('[ParentMenuRedirect] Error:', error);
router.replace(fallbackPath);
}
}, [router, parentPath, fallbackPath]);
// 리다이렉트 중 로딩 표시
return (
<div className="flex items-center justify-center min-h-[200px]">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
}