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

@@ -17,7 +17,7 @@
import { useState, useMemo, useCallback, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { format, startOfMonth, endOfMonth } from 'date-fns';
import { CreditCard, Plus, RefreshCw, Save, Loader2 } from 'lucide-react';
import { CreditCard, Plus, RefreshCw, Save, Loader2, Search } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
@@ -380,18 +380,66 @@ export function CardTransactionInquiry({
},
filterTitle: '카드 필터',
// 헤더 액션 (등록 버튼)
// 헤더 액션: 계정과목명 Select + 저장 + 새로고침
headerActions: () => (
<Button className="ml-auto" onClick={() => router.push('/ko/accounting/card-transactions?mode=new')}>
<Plus className="w-4 h-4 mr-2" />
</Button>
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700 whitespace-nowrap"></span>
<Select value={selectedAccountSubject} onValueChange={setSelectedAccountSubject}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{ACCOUNT_SUBJECT_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Button onClick={handleSaveAccountSubject} size="sm">
<Save className="h-4 w-4 mr-1" />
</Button>
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={isLoading}>
{isLoading ? (
<Loader2 className="h-4 w-4 mr-1 animate-spin" />
) : (
<RefreshCw className="h-4 w-4 mr-1" />
)}
{isLoading ? '조회중...' : '새로고침'}
</Button>
</div>
),
// 등록 버튼
createButton: {
label: '카드내역 등록',
icon: Plus,
onClick: () => router.push('/ko/accounting/card-transactions?mode=new'),
},
// 커스텀 필터 함수
customFilterFn: (items) => {
if (cardFilter === 'all') return items;
return items.filter((item) => item.cardName === cardFilter);
if (!items || items.length === 0) return items;
let result = [...items];
// 검색어 필터
if (searchQuery) {
const search = searchQuery.toLowerCase();
result = result.filter((item) =>
item.card.toLowerCase().includes(search) ||
item.cardName.toLowerCase().includes(search) ||
item.user.toLowerCase().includes(search) ||
item.merchantName.toLowerCase().includes(search)
);
}
// 카드명 필터
if (cardFilter !== 'all') {
result = result.filter((item) => item.cardName === cardFilter);
}
return result;
},
// 커스텀 정렬 함수
@@ -417,8 +465,15 @@ export function CardTransactionInquiry({
},
// 날짜 선택기 (헤더 액션)
// 검색창 (공통 컴포넌트에서 자동 생성)
hideSearch: true,
searchValue: searchQuery,
onSearchChange: setSearchQuery,
searchPlaceholder: '카드, 카드명, 사용자, 가맹점명 검색...',
dateRangeSelector: {
enabled: true,
showPresets: true,
startDate,
endDate,
onStartDateChange: setStartDate,
@@ -428,42 +483,6 @@ export function CardTransactionInquiry({
// 선택 항목 변경 콜백
onSelectionChange: setSelectedItems,
// 테이블 상단 콘텐츠 (계정과목명 + 저장 + 새로고침)
beforeTableContent: (
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700"></span>
<Select value={selectedAccountSubject} onValueChange={setSelectedAccountSubject}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="계정과목명 선택" />
</SelectTrigger>
<SelectContent>
{ACCOUNT_SUBJECT_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
onClick={handleSaveAccountSubject}
className="bg-blue-500 hover:bg-blue-600"
>
<Save className="h-4 w-4 mr-2" />
</Button>
</div>
<Button variant="outline" onClick={handleRefresh} disabled={isLoading}>
{isLoading ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<RefreshCw className="h-4 w-4 mr-2" />
)}
</Button>
</div>
),
// 테이블 헤더 액션 (2개 필터)
tableHeaderActions: () => (
<div className="flex items-center gap-2 flex-wrap">