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

@@ -1,7 +1,7 @@
"use client";
import { ReactNode, Fragment, useState, useEffect, useRef, useCallback } from "react";
import { LucideIcon, Trash2, Plus, Loader2, ArrowUpDown, ArrowUp, ArrowDown } from "lucide-react";
import { LucideIcon, Trash2, Plus, Loader2, ArrowUpDown, ArrowUp, ArrowDown, Search } from "lucide-react";
import { DateRangeSelector } from "@/components/molecules/DateRangeSelector";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent } from "@/components/ui/tabs";
@@ -17,6 +17,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
import { Input } from "@/components/ui/input";
import { PageLayout } from "@/components/organisms/PageLayout";
import { PageHeader } from "@/components/organisms/PageHeader";
import { StatCards } from "@/components/organisms/StatCards";
@@ -106,10 +107,14 @@ export interface IntegratedListTemplateV2Props<T = any> {
dateRangeSelector?: {
enabled: boolean;
showPresets?: boolean;
/** 날짜 입력 숨김 (검색창만 표시하고 싶을 때) */
hideDateInputs?: boolean;
startDate?: string;
endDate?: string;
onStartDateChange?: (date: string) => void;
onEndDateChange?: (date: string) => void;
/** 추가 액션 (검색창 등) - 프리셋 버튼 옆에 배치 */
extraActions?: ReactNode;
};
/**
* 등록 버튼 (오른쪽 끝 배치)
@@ -237,7 +242,7 @@ export function IntegratedListTemplateV2<T = any>({
onSearchChange,
searchPlaceholder = "검색...",
extraFilters,
hideSearch = false,
hideSearch = true, // 기본값: 타이틀 아래에 검색창 표시 (Card 안 SearchFilter 숨김)
tabs,
activeTab,
onTabChange,
@@ -536,32 +541,71 @@ export function IntegratedListTemplateV2<T = any>({
/>
{/* 헤더 액션 (달력, 버튼 등) - 타이틀 아래 배치 */}
{/* 레이아웃: [달력 (왼쪽)] -------------- [등록 버튼 (오른쪽 끝)] */}
{(dateRangeSelector?.enabled || createButton || headerActions) && (
{/* 레이아웃: [달력] [프리셋버튼] [검색창] -------------- [추가버튼들] [등록버튼] (오른쪽 끝) */}
{(dateRangeSelector?.enabled || createButton || headerActions || (hideSearch && onSearchChange)) && (
isLoading ? renderHeaderActionSkeleton() : (
<div className="flex items-center gap-2 flex-wrap w-full">
{/* 날짜 범위 선택기 (왼쪽) */}
{dateRangeSelector?.enabled && (
<div className="flex flex-col xl:flex-row xl:items-center gap-2 w-full">
{/* 날짜 범위 선택기 + 검색창 (왼쪽) */}
{dateRangeSelector?.enabled ? (
<DateRangeSelector
startDate={dateRangeSelector.startDate || ''}
endDate={dateRangeSelector.endDate || ''}
onStartDateChange={dateRangeSelector.onStartDateChange}
onEndDateChange={dateRangeSelector.onEndDateChange}
hidePresets={dateRangeSelector.showPresets === false}
hideDateInputs={dateRangeSelector.hideDateInputs}
extraActions={
<>
{/* hideSearch=true면 검색창 자동 추가 (extraActions 앞에) */}
{hideSearch && onSearchChange && (
<div className="relative w-full xl:w-[300px]">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder={searchPlaceholder}
value={searchValue || ''}
onChange={(e) => onSearchChange(e.target.value)}
className="pl-9 w-full bg-gray-50 border-gray-200"
/>
</div>
)}
{/* 기존 extraActions (추가 버튼 등) */}
{dateRangeSelector.extraActions}
</>
}
/>
) : (
/* dateRangeSelector 없어도 hideSearch=true면 검색창 표시 */
hideSearch && onSearchChange && (
<div className="relative w-full xl:w-[300px]">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder={searchPlaceholder}
value={searchValue || ''}
onChange={(e) => onSearchChange(e.target.value)}
className="pl-9 w-full bg-gray-50 border-gray-200"
/>
</div>
)
)}
{/* 레거시 헤더 액션 (기존 호환성 유지) */}
{headerActions}
{/* 등록 버튼 (오른쪽 끝) */}
{createButton && (
<Button className="ml-auto" onClick={createButton.onClick}>
{createButton.icon ? (
<createButton.icon className="h-4 w-4 mr-2" />
) : (
<Plus className="h-4 w-4 mr-2" />
{/* 버튼 영역 (오른쪽 끝으로 통합) */}
{(headerActions || createButton) && (
<div className="flex items-center gap-2 ml-auto shrink-0">
{/* 헤더 액션 (엑셀 다운로드 등 추가 버튼들) */}
{headerActions}
{/* 등록 버튼 */}
{createButton && (
<Button onClick={createButton.onClick}>
{createButton.icon ? (
<createButton.icon className="h-4 w-4 mr-2" />
) : (
<Plus className="h-4 w-4 mr-2" />
)}
{createButton.label}
</Button>
)}
{createButton.label}
</Button>
</div>
)}
</div>
)