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:
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user